BidiFormatter.java revision c92d608279e5716a176e142abcd5e1b2148a0680
177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio/*
277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Copyright (C) 2013 The Android Open Source Project
377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *
477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Licensed under the Apache License, Version 2.0 (the "License");
577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * you may not use this file except in compliance with the License.
677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * You may obtain a copy of the License at
777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *
877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *      http://www.apache.org/licenses/LICENSE-2.0
977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *
1077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Unless required by applicable law or agreed to in writing, software
1177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * distributed under the License is distributed on an "AS IS" BASIS,
1277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * See the License for the specific language governing permissions and
1477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * limitations under the License.
1577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */
1677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
1777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Megliopackage android.support.v4.text.bidi;
1877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
1977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglioimport android.support.v4.text.TextDirectionHeuristicCompat;
2077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglioimport android.support.v4.text.TextDirectionHeuristicsCompat;
2177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglioimport android.support.v4.text.TextUtilsCompat;
2277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglioimport android.support.v4.view.ViewCompat;
2377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
2477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglioimport java.util.Locale;
2577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
2677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglioimport static android.support.v4.text.TextDirectionHeuristicsCompat.FIRSTSTRONG_LTR;
2777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
2877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio/**
2977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Utility class for formatting text for display in a potentially opposite-directionality context
3077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * without garbling. The directionality of the context is set at formatter creation and the
3177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality of the text can be either estimated or passed in when known. Provides the
3277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * following functionality:
3377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p>
3477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 1. Bidi Wrapping
3577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * When text in one language is mixed into a document in another, opposite-directionality language,
3677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * e.g. when an English business name is embedded in a Hebrew web page, both the inserted string
3777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * and the text surrounding it may be displayed incorrectly unless the inserted string is explicitly
3877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * separated from the surrounding text in a "wrapper" that:
3977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p>
4077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * - Declares its directionality so that the string is displayed correctly. This can be done in HTML
4177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   markup (e.g. a 'span dir="rtl"' element) by {@link #spanWrap} and similar methods, or - only in
4277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   contexts where markup can't be used - in Unicode bidi formatting codes by {@link #unicodeWrap}
4377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   and similar methods.
4477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p>
4577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * - Isolates the string's directionality, so it does not unduly affect the surrounding content.
4677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   Currently, this can only be done using invisible Unicode characters of the same direction as
4777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   the context (LRM or RLM) in addition to the directionality declaration above, thus "resetting"
4877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   the directionality to that of the context. The "reset" may need to be done at both ends of the
4977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   string. Without "reset" after the string, the string will "stick" to a number or logically
5077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   separate opposite-direction text that happens to follow it in-line (even if separated by
5177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   neutral content like spaces and punctuation). Without "reset" before the string, the same can
5277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   happen there, but only with more opposite-direction text, not a number. One approach is to
5377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   "reset" the direction only after each string, on the theory that if the preceding opposite-
5477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   direction text is itself bidi-wrapped, the "reset" after it will prevent the sticking. (Doing
5577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   the "reset" only before each string definitely does not work because we do not want to require
5677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   bidi-wrapping numbers, and a bidi-wrapped opposite-direction string could be followed by a
5777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   number.) Still, the safest policy is to do the "reset" on both ends of each string, since RTL
5877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   message translations often contain untranslated Latin-script brand names and technical terms,
5977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   and one of these can be followed by a bidi-wrapped inserted value. On the other hand, when one
6077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   has such a message, it is best to do the "reset" manually in the message translation itself,
6177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   since the message's opposite-direction text could be followed by an inserted number, which we
6277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   would not bidi-wrap anyway. Thus, "reset" only after the string is the current default. In an
6377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   alternative to "reset", recent additions to the HTML, CSS, and Unicode standards allow the
6477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   isolation to be part of the directionality declaration. This form of isolation is better than
6577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   "reset" because it takes less space, does not require knowing the context directionality, has a
6677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   gentler effect than "reset", and protects both ends of the string. However, we do not yet allow
6777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   using it because required platforms do not yet support it.
6877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p>
6977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Providing these wrapping services is the basic purpose of the bidi formatter.
7077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p>
7177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 2. Directionality estimation
7277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * How does one know whether a string about to be inserted into surrounding text has the same
7377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality? Well, in many cases, one knows that this must be the case when writing the code
7477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * doing the insertion, e.g. when a localized message is inserted into a localized page. In such
7577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * cases there is no need to involve the bidi formatter at all. In some other cases, it need not be
7677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * the same as the context, but is either constant (e.g. urls are always LTR) or otherwise known.
7777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * In the remaining cases, e.g. when the string is user-entered or comes from a database, the
7877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * language of the string (and thus its directionality) is not known a priori, and must be
7977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * estimated at run-time. The bidi formatter can do this automatically using the default
8077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * first-strong estimation algorithm. It can also be configured to use a custom directionality
8177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * estimation object.
8277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p>
8377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 3. Escaping
8477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * When wrapping plain text - i.e. text that is not already HTML or HTML-escaped - in HTML markup,
8577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * the text must first be HTML-escaped to prevent XSS attacks and other nasty business. This of
8677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * course is always true, but the escaping can not be done after the string has already been wrapped
8777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * in markup, so the bidi formatter also serves as a last chance and includes escaping services.
8877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p>
8977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Thus, in a single call, the formatter will escape the input string as specified, determine its
9077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality, and wrap it as necessary. It is then up to the caller to insert the return value
9177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * in the output.
9277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */
9377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Megliopublic final class BidiFormatter {
9477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
9577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
9677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * The default text direction heuristic.
9777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
9877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static TextDirectionHeuristicCompat DEFAULT_TEXT_DIRECTION_HEURISTIC = FIRSTSTRONG_LTR;
9977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
10077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
10177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Unicode "Left-To-Right Embedding" (LRE) character.
10277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
10377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final char LRE = '\u202A';
10477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
10577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
10677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Unicode "Right-To-Left Embedding" (RLE) character.
10777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
10877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final char RLE = '\u202B';
10977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
11077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
11177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Unicode "Pop Directional Formatting" (PDF) character.
11277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
11377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final char PDF = '\u202C';
11477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
11577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
11677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *  Unicode "Left-To-Right Mark" (LRM) character.
11777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
11877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final char LRM = '\u200E';
11977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
12077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /*
12177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Unicode "Right-To-Left Mark" (RLM) character.
12277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
12377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final char RLM = '\u200F';
12477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
12577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /*
12677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * String representation of LRM
12777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
12877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final String LRM_STRING = Character.toString(LRM);
12977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
13077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /*
13177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * String representation of RLM
13277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
13377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final String RLM_STRING = Character.toString(RLM);
13477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
13577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
13677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * "ltr" string constant.
13777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
13877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final String LTR_STRING = "ltr";
13977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
14077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
14177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * "rtl" string constant.
14277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
14377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final String RTL_STRING = "rtl";
14477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
14577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
14677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * "dir=\"ltr\"" string constant.
14777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
14877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final String DIR_LTR_STRING = "dir=\"ltr\"";
14977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
15077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
15177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * "dir=\"rtl\"" string constant.
15277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
15377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final String DIR_RTL_STRING = "dir=\"rtl\"";
15477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
15577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
15677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * "right" string constant.
15777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
15877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final String RIGHT = "right";
15977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
16077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
16177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * "left" string constant.
16277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
16377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final String LEFT = "left";
16477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
16577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
16677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Empty string constant.
16777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
16877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final String EMPTY_STRING = "";
16977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
17077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
17177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * A class for building a BidiFormatter with non-default options.
17277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
17377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public static final class Builder {
174c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        private boolean mIsRtlContext;
175c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        private int mFlags;
176c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        private TextDirectionHeuristicCompat mTextDirectionHeuristicCompat;
17777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
17877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
17977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Constructor.
18077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *
18177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
18277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        public Builder() {
18377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            initialize(isRtlLocale(Locale.getDefault()));
18477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
18577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
18677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
18777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Constructor.
18877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *
18977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @param rtlContext Whether the context directionality is RTL.
19077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
19177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        public Builder(boolean rtlContext) {
19277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            initialize(rtlContext);
19377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
19477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
19577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
19677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Constructor.
19777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *
19877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @param locale The context locale.
19977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
20077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        public Builder(Locale locale) {
20177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            initialize(isRtlLocale(locale));
20277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
20377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
20477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
20577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Initializes the builder with the given context directionality and default options.
20677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *
20777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @param isRtlContext Whether the context is RTL or not.
20877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
20977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private void initialize(boolean isRtlContext) {
210c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio            mIsRtlContext = isRtlContext;
211c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio            mTextDirectionHeuristicCompat = DEFAULT_TEXT_DIRECTION_HEURISTIC;
212c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio            mFlags = DEFAULT_FLAGS;
21377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
21477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
21577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
21677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Specifies whether the BidiFormatter to be built should also "reset" directionality before
21777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * a string being bidi-wrapped, not just after it. The default is false.
21877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
21977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        public Builder stereoReset(boolean stereoReset) {
22077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (stereoReset) {
221c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio                mFlags |= FLAG_STEREO_RESET;
22277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            } else {
223c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio                mFlags &= ~FLAG_STEREO_RESET;
22477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
22577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return this;
22677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
22777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
22877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
22977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Specifies the default directionality estimation algorithm to be used by the BidiFormatter.
23077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * By default, uses the first-strong heuristic.
23177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *
23277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @param heuristic the {@code TextDirectionHeuristic} to use.
23377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @return the builder itself.
23477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
23577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        public Builder setTextDirectionHeuristic(TextDirectionHeuristicCompat heuristic) {
236c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio            mTextDirectionHeuristicCompat = heuristic;
23777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return this;
23877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
23977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
24077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private static BidiFormatter getDefaultInstanceFromContext(boolean isRtlContext) {
24177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return isRtlContext ? DEFAULT_RTL_INSTANCE : DEFAULT_LTR_INSTANCE;
24277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
24377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
24477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
24577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @return A BidiFormatter with the specified options.
24677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
24777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        public BidiFormatter build() {
248c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio            if (mFlags == DEFAULT_FLAGS &&
249c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio                    mTextDirectionHeuristicCompat == DEFAULT_TEXT_DIRECTION_HEURISTIC) {
250c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio                return getDefaultInstanceFromContext(mIsRtlContext);
25177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
252c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio            return new BidiFormatter(mIsRtlContext, mFlags, mTextDirectionHeuristicCompat);
25377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
25477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
25577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
25677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    //
25777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final int FLAG_STEREO_RESET = 2;
25877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final int DEFAULT_FLAGS = FLAG_STEREO_RESET;
25977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
26077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final BidiFormatter DEFAULT_LTR_INSTANCE = new BidiFormatter(
26177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            false /* LTR context */,
26277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            DEFAULT_FLAGS,
26377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            DEFAULT_TEXT_DIRECTION_HEURISTIC);
26477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
26577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final BidiFormatter DEFAULT_RTL_INSTANCE = new BidiFormatter(
26677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            true /* RTL context */,
26777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            DEFAULT_FLAGS,
26877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            DEFAULT_TEXT_DIRECTION_HEURISTIC);
26977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
270c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio    private final boolean mIsRtlContext;
271c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio    private final int mFlags;
272c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio    private final TextDirectionHeuristicCompat mDefaultTextDirectionHeuristicCompat;
27377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
27477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
27577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Factory for creating an instance of BidiFormatter given the context directionality.
27677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
27777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param rtlContext Whether the context directionality is RTL.
27877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
27977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public static BidiFormatter getInstance(boolean rtlContext) {
28077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return new Builder(rtlContext).build();
28177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
28277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
28377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
28477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Factory for creating an instance of BidiFormatter given the context locale.
28577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
28677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param locale The context locale.
28777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
28877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public static BidiFormatter getInstance(Locale locale) {
28977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return new Builder(locale).build();
29077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
29177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
29277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
29377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param isRtlContext Whether the context directionality is RTL or not.
29477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param flags The option flags.
29577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The default text direction heuristic.
29677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
29777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private BidiFormatter(boolean isRtlContext, int flags, TextDirectionHeuristicCompat heuristic) {
298c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        mIsRtlContext = isRtlContext;
299c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        mFlags = flags;
300c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        mDefaultTextDirectionHeuristicCompat = heuristic;
30177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
30277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
30377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
30477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Whether the context directionality is RTL
30577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
30677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public boolean isRtlContext() {
307c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return mIsRtlContext;
30877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
30977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
31077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
31177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Whether directionality "reset" should also be done before a string being
31277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * bidi-wrapped, not just after it.
31377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
31477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public boolean getStereoReset() {
315c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return (mFlags & FLAG_STEREO_RESET) != 0;
31677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
31777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
31877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
31977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns "rtl" if {@code str}'s estimated directionality is RTL, and "ltr" if it is LTR.
32077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
32177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str String whose directionality is to be estimated.
32277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return "rtl" if {@code str}'s estimated directionality is RTL, and "ltr" otherwise.
32377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
32477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String dirAttrValue(String str) {
32577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return dirAttrValue(isRtl(str));
32677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
32777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
32877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
32977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #dirAttrValue(String)}, but uses a given heuristic to estimate the
33077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * {@code str}'s directionality.
33177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
33277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str String whose directionality is to be estimated.
33377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s
33477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *                  directionality.
33577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return "rtl" if {@code str}'s estimated directionality is RTL, and "ltr" otherwise.
33677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
33777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String dirAttrValue(String str, TextDirectionHeuristicCompat heuristic) {
33877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return dirAttrValue(heuristic.isRtl(str, 0, str.length()));
33977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
34077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
34177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
34277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns "rtl" if the given directionality is RTL, and "ltr" if it is LTR.
34377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
34477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param isRtl Whether the directionality is RTL or not.
34577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return "rtl" if the given directionality is RTL, and "ltr" otherwise.
34677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
34777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String dirAttrValue(boolean isRtl) {
34877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return isRtl ? RTL_STRING : LTR_STRING;
34977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
35077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
35177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
35277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns "dir=\"ltr\"" or "dir=\"rtl\"", depending on {@code str}'s estimated directionality,
35377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * if it is not the same as the context directionality. Otherwise, returns the empty string.
35477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
35577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str String whose directionality is to be estimated.
35677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return "dir=\"rtl\"" for RTL text in non-RTL context; "dir=\"ltr\"" for LTR text in non-LTR
35777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     context; else, the empty string.
35877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
35977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String dirAttr(String str) {
36077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return dirAttr(isRtl(str));
36177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
36277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
36377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
36477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #dirAttr(String)}, but uses a given heuristic to estimate the
36577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * {@code str}'s directionality.
36677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
36777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str String whose directionality is to be estimated.
36877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s
36977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *                  directionality.
37077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return "dir=\"rtl\"" for RTL text in non-RTL context; "dir=\"ltr\"" for LTR text in non-LTR
37177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     context; else, the empty string.
37277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
37377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String dirAttr(String str, TextDirectionHeuristicCompat heuristic) {
37477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return dirAttr(heuristic.isRtl(str, 0, str.length()));
37577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
37677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
37777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
37877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns "dir=\"ltr\"" or "dir=\"rtl\"", depending on the given directionality, if it is not
37977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * the same as the context directionality. Otherwise, returns the empty string.
38077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
38177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param isRtl Whether the directionality is RTL or not
38277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return "dir=\"rtl\"" for RTL text in non-RTL context; "dir=\"ltr\"" for LTR text in non-LTR
38377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     context; else, the empty string.
38477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
38577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String dirAttr(boolean isRtl) {
386c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return (isRtl != mIsRtlContext) ? (isRtl ? DIR_RTL_STRING :  DIR_LTR_STRING) : EMPTY_STRING;
38777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
38877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
38977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
39077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns a Unicode bidi mark matching the context directionality (LRM or RLM) if either the
39177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * overall or the exit directionality of a given string is opposite to the context directionality.
39277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Putting this after the string (including its directionality declaration wrapping) prevents it
39377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * from "sticking" to other opposite-directionality text or a number appearing after it inline
39477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * with only neutral content in between. Otherwise returns the empty string. While the exit
39577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * directionality is determined by scanning the end of the string, the overall directionality is
39677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * given explicitly in {@code dir}.
39777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
39877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str String after which the mark may need to appear.
39977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context;
40077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     else, the empty string.
40177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
40277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String markAfter(String str) {
403c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return markAfter(str, mDefaultTextDirectionHeuristicCompat);
40477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
40577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
40677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
40777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #markAfter(String)}, but uses a given heuristic to estimate the
40877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * {@code str}'s directionality.
40977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
41077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str String after which the mark may need to appear.
41177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s
41277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *                  directionality.
41377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context;
41477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     else, the empty string.
41577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
41677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String markAfter(String str, TextDirectionHeuristicCompat heuristic) {
41777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        final boolean isRtl = heuristic.isRtl(str, 0, str.length());
41877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        // getExitDir() is called only if needed (short-circuit).
419c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        if (!mIsRtlContext && (isRtl || getExitDir(str) == DIR_RTL)) {
42077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return LRM_STRING;
42177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
422c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        if (mIsRtlContext && (!isRtl || getExitDir(str) == DIR_LTR)) {
42377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return RLM_STRING;
42477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
42577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return EMPTY_STRING;
42677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
42777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
42877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
42977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns a Unicode bidi mark matching the context directionality (LRM or RLM) if either the
43077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * overall or the entry directionality of a given string is opposite to the context
43177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * directionality. Putting this before the string (including its directionality declaration
43277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * wrapping) prevents it from "sticking" to other opposite-directionality text appearing before it
43377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * inline with only neutral content in between. Otherwise returns the empty string. While the
43477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * entry directionality is determined by scanning the beginning of the string, the overall
43577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * directionality is given explicitly in {@code dir}.
43677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
43777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str String before which the mark may need to appear.
43877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context;
43977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     else, the empty string.
44077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
44177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String markBefore(String str) {
442c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return markBefore(str, mDefaultTextDirectionHeuristicCompat);
44377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
44477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
44577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
44677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #markBefore(String)}, but uses a given heuristic to estimate the
44777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * {@code str}'s directionality.
44877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
44977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str String before which the mark may need to appear.
45077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s
45177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *                  directionality.
45277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context;
45377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     else, the empty string.
45477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
45577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String markBefore(String str, TextDirectionHeuristicCompat heuristic) {
45677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        final boolean isRtl = heuristic.isRtl(str, 0, str.length());
45777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        // getEntryDir() is called only if needed (short-circuit).
458c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        if (!mIsRtlContext && (isRtl || getEntryDir(str) == DIR_RTL)) {
45977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return LRM_STRING;
46077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
461c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        if (mIsRtlContext && (!isRtl || getEntryDir(str) == DIR_LTR)) {
46277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return RLM_STRING;
46377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
46477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return EMPTY_STRING;
46577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
46677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
46777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
46877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns the Unicode bidi mark matching the context directionality (LRM for LTR context
46977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * directionality, RLM for RTL context directionality).
47077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
47177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String mark() {
472c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return mIsRtlContext ? RLM_STRING : LRM_STRING;
47377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
47477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
47577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
47677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns "right" for RTL context directionality. Otherwise for LTR context directionality
47777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * returns "left".
47877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
47977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String startEdge() {
480c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return mIsRtlContext ? RIGHT : LEFT;
48177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
48277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
48377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
48477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns "left" for RTL context directionality. Otherwise for LTR context directionality
48577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * returns "right".
48677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
48777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String endEdge() {
488c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return mIsRtlContext ? LEFT : RIGHT;
48977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
49077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
49177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
49277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Estimates the directionality of a string using the default text direction heuristic.
49377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
49477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str String whose directionality is to be estimated.
49577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return true if {@code str}'s estimated overall directionality is RTL. Otherwise returns
49677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *          false.
49777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
49877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public boolean isRtl(String str) {
499c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return mDefaultTextDirectionHeuristicCompat.isRtl(str, 0, str.length());
50077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
50177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
50277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
50377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Formats a given string of unknown directionality for use in HTML output of the context
50477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * directionality, so an opposite-directionality string is neither garbled nor garbles its
50577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * surroundings.
50677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * <p>
50777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * The algorithm: estimates the directionality of the given string using the given heuristic.
50877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * If the directionality is known, pass TextDirectionHeuristics.LTR or RTL for heuristic.
50977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * In case its directionality doesn't match the context directionality, wraps it with a 'span'
51077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * element and adds a "dir" attribute (either 'dir=\"rtl\"' or 'dir=\"ltr\"').
51177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * <p>
51277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * If {@code isolate}, directionally isolates the string so that it does not garble its
51377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * surroundings. Currently, this is done by "resetting" the directionality after the string by
51477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * appending a trailing Unicode bidi mark matching the context directionality (LRM or RLM) when
51577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * either the overall directionality or the exit directionality of the string is opposite to that
51677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * of the context. If the formatter was built using {@link Builder#stereoReset(boolean)} and
51777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * passing "true" as an argument, also prepends a Unicode bidi mark matching the context
51877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * directionality when either the overall directionality or the entry directionality of the
51977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * string is opposite to that of the context.
52077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * <p>
52177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
52277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
52377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The algorithm to be used to estimate the string's overall direction.
52477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param isolate Whether to directionally isolate the string to prevent it from garbling the
52577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     content around it.
52677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Input string after applying the above processing.
52777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
52877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String spanWrap(String str, TextDirectionHeuristicCompat heuristic, boolean isolate) {
52977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        final boolean isRtl = heuristic.isRtl(str, 0, str.length());
53077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        String origStr = str;
53177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        str = TextUtilsCompat.htmlEncode(str);
53277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
53377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        StringBuilder result = new StringBuilder();
53477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        if (getStereoReset() && isolate) {
53577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(markBefore(origStr,
53677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    isRtl ? TextDirectionHeuristicsCompat.RTL : TextDirectionHeuristicsCompat.LTR));
53777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
538c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        if (isRtl != mIsRtlContext) {
53977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append("<span ").append(dirAttr(isRtl)).append('>').append(str).append("</span>");
54077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        } else {
54177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(str);
54277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
54377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        if (isolate) {
54477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(markAfter(origStr,
54577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    isRtl ? TextDirectionHeuristicsCompat.RTL : TextDirectionHeuristicsCompat.LTR));
54677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
54777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return result.toString();
54877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
54977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
55077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
55177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #spanWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but assumes
55277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * {@code isolate} is true.
55377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
55477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
55577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The algorithm to be used to estimate the string's overall direction.
55677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Input string after applying the above processing.
55777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
55877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String spanWrap(String str, TextDirectionHeuristicCompat heuristic) {
55977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return spanWrap(str, heuristic, true /* isolate */);
56077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
56177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
56277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
56377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #spanWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but uses the
56477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * formatter's default direction estimation algorithm.
56577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
56677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
56777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param isolate Whether to directionally isolate the string to prevent it from garbling the
56877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     content around it
56977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Input string after applying the above processing.
57077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
57177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String spanWrap(String str, boolean isolate) {
572c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return spanWrap(str, mDefaultTextDirectionHeuristicCompat, isolate);
57377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
57477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
57577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
57677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #spanWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but uses the
57777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * formatter's default direction estimation algorithm and assumes {@code isolate} is true.
57877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
57977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
58077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Input string after applying the above processing.
58177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
58277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String spanWrap(String str) {
583c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return spanWrap(str, mDefaultTextDirectionHeuristicCompat, true /* isolate */);
58477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
58577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
58677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
58777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Formats a string of given directionality for use in plain-text output of the context
58877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * directionality, so an opposite-directionality string is neither garbled nor garbles its
58977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * surroundings. As opposed to {@link #spanWrap}, this makes use of Unicode bidi
59077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * formatting characters. In HTML, its *only* valid use is inside of elements that do not allow
59177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * markup, e.g. the 'option' and 'title' elements.
59277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * <p>
59377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * The algorithm: In case the given directionality doesn't match the context directionality, wraps
59477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * the string with Unicode bidi formatting characters: RLE+{@code str}+PDF for RTL text, or
59577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * LRE+{@code str}+PDF for LTR text.
59677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * <p>
59777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * If {@code isolate}, directionally isolates the string so that it does not garble its
59877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * surroundings. Currently, this is done by "resetting" the directionality after the string by
59977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * appending a trailing Unicode bidi mark matching the context directionality (LRM or RLM) when
60077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * either the overall directionality or the exit directionality of the string is opposite to that
60177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * of the context. If the formatter was built using {@link Builder#stereoReset(boolean)} and
60277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * passing "true" as an argument, also prepends a Unicode bidi mark matching the context
60377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * directionality when either the overall directionality or the entry directionality of the
60477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * string is opposite to that of the context. Note that as opposed to the overall
60577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * directionality, the entry and exit directionalities are determined from the string itself.
60677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * <p>
60777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Does *not* do HTML-escaping.
60877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
60977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
61077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The algorithm to be used to estimate the string's overall direction.
61177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param isolate Whether to directionally isolate the string to prevent it from garbling the
61277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     content around it
61377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Input string after applying the above processing.
61477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
61577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String unicodeWrap(String str, TextDirectionHeuristicCompat heuristic, boolean isolate) {
61677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        final boolean isRtl = heuristic.isRtl(str, 0, str.length());
61777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        StringBuilder result = new StringBuilder();
61877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        if (getStereoReset() && isolate) {
61977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(markBefore(str,
62077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    isRtl ? TextDirectionHeuristicsCompat.RTL : TextDirectionHeuristicsCompat.LTR));
62177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
622c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        if (isRtl != mIsRtlContext) {
62377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(isRtl ? RLE : LRE);
62477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(str);
62577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(PDF);
62677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        } else {
62777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(str);
62877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
62977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        if (isolate) {
63077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(markAfter(str,
63177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    isRtl ? TextDirectionHeuristicsCompat.RTL : TextDirectionHeuristicsCompat.LTR));
63277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
63377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return result.toString();
63477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
63577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
63677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
63777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #unicodeWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but assumes
63877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * {@code isolate} is true.
63977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
64077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
64177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The algorithm to be used to estimate the string's overall direction.
64277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Input string after applying the above processing.
64377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
64477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String unicodeWrap(String str, TextDirectionHeuristicCompat heuristic) {
64577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return unicodeWrap(str, heuristic, true /* isolate */);
64677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
64777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
64877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
64977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #unicodeWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but uses the
65077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * formatter's default direction estimation algorithm.
65177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
65277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
65377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param isolate Whether to directionally isolate the string to prevent it from garbling the
65477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     content around it
65577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Input string after applying the above processing.
65677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
65777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String unicodeWrap(String str, boolean isolate) {
658c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return unicodeWrap(str, mDefaultTextDirectionHeuristicCompat, isolate);
65977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
66077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
66177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
66277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #unicodeWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but uses the
66377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * formatter's default direction estimation algorithm and assumes {@code isolate} is true.
66477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
66577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
66677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Input string after applying the above processing.
66777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
66877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String unicodeWrap(String str) {
669c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return unicodeWrap(str, mDefaultTextDirectionHeuristicCompat, true /* isolate */);
67077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
67177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
67277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
67377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Helper method to return true if the Locale directionality is RTL.
67477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
67577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param locale The Locale whose directionality will be checked to be RTL or LTR
67677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return true if the {@code locale} directionality is RTL. False otherwise.
67777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
67877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static boolean isRtlLocale(Locale locale) {
67977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return (TextUtilsCompat.getLayoutDirectionFromLocale(locale) == ViewCompat.LAYOUT_DIRECTION_RTL);
68077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
68177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
68277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
68377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Enum for directionality type.
68477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
68577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final int DIR_LTR = -1;
68677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final int DIR_UNKNOWN = 0;
68777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final int DIR_RTL = +1;
68877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
68977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
69077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns the directionality of the last character with strong directionality in the string, or
69177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * DIR_UNKNOWN if none was encountered. For efficiency, actually scans backwards from the end of
69277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * the string. Treats a non-BN character between an LRE/RLE/LRO/RLO and its matching PDF as a
69377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * strong character, LTR after LRE/LRO, and RTL after RLE/RLO. The results are undefined for a
69477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * string containing unbalanced LRE/RLE/LRO/RLO/PDF characters. The intended use is to check
69577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * whether a logically separate item that starts with a number or a character of the string's
69677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * exit directionality and follows this string inline (not counting any neutral characters in
69777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * between) would "stick" to it in an opposite-directionality context, thus being displayed in
69877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * an incorrect position. An LRM or RLM character (the one of the context's directionality)
69977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * between the two will prevent such sticking.
70077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
70177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str the string to check.
70277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
70377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static int getExitDir(String str) {
70477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return new DirectionalityEstimator(str, false /* isHtml */).getExitDir();
70577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
70677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
70777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
70877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns the directionality of the first character with strong directionality in the string,
70977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * or DIR_UNKNOWN if none was encountered. Treats a non-BN character between an
71077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * LRE/RLE/LRO/RLO and its matching PDF as a strong character, LTR after LRE/LRO, and RTL after
71177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * RLE/RLO. The results are undefined for a string containing unbalanced LRE/RLE/LRO/RLO/PDF
71277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * characters. The intended use is to check whether a logically separate item that ends with a
71377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * character of the string's entry directionality and precedes the string inline (not counting
71477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * any neutral characters in between) would "stick" to it in an opposite-directionality context,
71577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * thus being displayed in an incorrect position. An LRM or RLM character (the one of the
71677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * context's directionality) between the two will prevent such sticking.
71777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
71877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str the string to check.
71977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
72077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static int getEntryDir(String str) {
72177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return new DirectionalityEstimator(str, false /* isHtml */).getEntryDir();
72277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
72377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
72477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
72577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * An object that estimates the directionality of a given string by various methods.
72677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
72777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
72877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static class DirectionalityEstimator {
72977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
73077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        // Internal static variables and constants.
73177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
73277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
73377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Size of the bidi character class cache. The results of the Character.getDirectionality()
73477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * calls on the lowest DIR_TYPE_CACHE_SIZE codepoints are kept in an array for speed.
73577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The 0x700 value is designed to leave all the European and Near Eastern languages in the
73677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * cache. It can be reduced to 0x180, restricting the cache to the Western European
73777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * languages.
73877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
73977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private static final int DIR_TYPE_CACHE_SIZE = 0x700;
74077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
74177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
74277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The bidi character class cache.
74377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
74477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private static final byte DIR_TYPE_CACHE[];
74577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
74677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        static {
74777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            DIR_TYPE_CACHE = new byte[DIR_TYPE_CACHE_SIZE];
74877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            for (int i = 0; i < DIR_TYPE_CACHE_SIZE; i++) {
74977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                DIR_TYPE_CACHE[i] = Character.getDirectionality(i);
75077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
75177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
75277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
75377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        // Internal instance variables.
75477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
75577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
75677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The text to be scanned.
75777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
75877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private final String text;
75977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
76077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
76177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Whether the text to be scanned is to be treated as HTML, i.e. skipping over tags and
76277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * entities when looking for the next / preceding dir type.
76377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
76477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private final boolean isHtml;
76577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
76677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
76777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The length of the text in chars.
76877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
76977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private final int length;
77077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
77177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
77277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The current position in the text.
77377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
77477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private int charIndex;
77577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
77677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
77777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The char encountered by the last dirTypeForward or dirTypeBackward call. If it
77877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * encountered a supplementary codepoint, this contains a char that is not a valid
77977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * codepoint. This is ok, because this member is only used to detect some well-known ASCII
78077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * syntax, e.g. "http://" and the beginning of an HTML tag or entity.
78177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
78277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private char lastChar;
78377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
78477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
78577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Constructor.
78677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *
78777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @param text The string to scan.
78877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @param isHtml Whether the text to be scanned is to be treated as HTML, i.e. skipping over
78977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *     tags and entities.
79077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
79177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        DirectionalityEstimator(String text, boolean isHtml) {
79277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            this.text = text;
79377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            this.isHtml = isHtml;
79477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            length = text.length();
79577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
79677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
79777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
79877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Returns the directionality of the first character with strong directionality in the
79977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * string, or DIR_UNKNOWN if none was encountered. Treats a non-BN character between an
80077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * LRE/RLE/LRO/RLO and its matching PDF as a strong character, LTR after LRE/LRO, and RTL
80177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * after RLE/RLO. The results are undefined for a string containing unbalanced
80277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * LRE/RLE/LRO/RLO/PDF characters.
80377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
80477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        int getEntryDir() {
80577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // The reason for this method name, as opposed to getFirstStrongDir(), is that
80677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // "first strong" is a commonly used description of Unicode's estimation algorithm,
80777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // but the two must treat formatting characters quite differently. Thus, we are staying
80877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // away from both "first" and "last" in these method names to avoid confusion.
80977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex = 0;
81077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int embeddingLevel = 0;
81177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int embeddingLevelDir = DIR_UNKNOWN;
81277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int firstNonEmptyEmbeddingLevel = 0;
81377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex < length && firstNonEmptyEmbeddingLevel == 0) {
81477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                switch (dirTypeForward()) {
81577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
81677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
81777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        ++embeddingLevel;
81877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        embeddingLevelDir = DIR_LTR;
81977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
82077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
82177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
82277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        ++embeddingLevel;
82377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        embeddingLevelDir = DIR_RTL;
82477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
82577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT:
82677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        --embeddingLevel;
82777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        // To restore embeddingLevelDir to its previous value, we would need a
82877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        // stack, which we want to avoid. Thus, at this point we do not know the
82977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        // current embedding's directionality.
83077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        embeddingLevelDir = DIR_UNKNOWN;
83177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
83277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_BOUNDARY_NEUTRAL:
83377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
83477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
83577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (embeddingLevel == 0) {
83677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_LTR;
83777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
83877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        firstNonEmptyEmbeddingLevel = embeddingLevel;
83977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
84077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
84177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
84277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (embeddingLevel == 0) {
84377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_RTL;
84477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
84577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        firstNonEmptyEmbeddingLevel = embeddingLevel;
84677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
84777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    default:
84877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        firstNonEmptyEmbeddingLevel = embeddingLevel;
84977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
85077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
85177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
85277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
85377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // We have either found a non-empty embedding or scanned the entire string finding
85477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // neither a non-empty embedding nor a strong character outside of an embedding.
85577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (firstNonEmptyEmbeddingLevel == 0) {
85677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                // We have not found a non-empty embedding. Thus, the string contains neither a
85777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                // non-empty embedding nor a strong character outside of an embedding.
85877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                return DIR_UNKNOWN;
85977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
86077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
86177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // We have found a non-empty embedding.
86277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (embeddingLevelDir != DIR_UNKNOWN) {
86377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                // We know the directionality of the non-empty embedding.
86477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                return embeddingLevelDir;
86577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
86677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
86777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // We do not remember the directionality of the non-empty embedding we found. So, we go
86877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // backwards to find the start of the non-empty embedding and get its directionality.
86977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex > 0) {
87077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                switch (dirTypeBackward()) {
87177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
87277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
87377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (firstNonEmptyEmbeddingLevel == embeddingLevel) {
87477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_LTR;
87577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
87677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        --embeddingLevel;
87777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
87877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
87977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
88077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (firstNonEmptyEmbeddingLevel == embeddingLevel) {
88177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_RTL;
88277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
88377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        --embeddingLevel;
88477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
88577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT:
88677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        ++embeddingLevel;
88777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
88877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
88977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
89077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // We should never get here.
89177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return DIR_UNKNOWN;
89277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
89377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
89477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
89577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Returns the directionality of the last character with strong directionality in the
89677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * string, or DIR_UNKNOWN if none was encountered. For efficiency, actually scans backwards
89777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * from the end of the string. Treats a non-BN character between an LRE/RLE/LRO/RLO and its
89877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * matching PDF as a strong character, LTR after LRE/LRO, and RTL after RLE/RLO. The results
89977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * are undefined for a string containing unbalanced LRE/RLE/LRO/RLO/PDF characters.
90077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
90177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        int getExitDir() {
90277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // The reason for this method name, as opposed to getLastStrongDir(), is that "last
90377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // strong" sounds like the exact opposite of "first strong", which is a commonly used
90477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // description of Unicode's estimation algorithm (getUnicodeDir() above), but the two
90577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // must treat formatting characters quite differently. Thus, we are staying away from
90677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // both "first" and "last" in these method names to avoid confusion.
90777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex = length;
90877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int embeddingLevel = 0;
90977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int lastNonEmptyEmbeddingLevel = 0;
91077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex > 0) {
91177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                switch (dirTypeBackward()) {
91277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
91377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (embeddingLevel == 0) {
91477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_LTR;
91577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
91677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (lastNonEmptyEmbeddingLevel == 0) {
91777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            lastNonEmptyEmbeddingLevel = embeddingLevel;
91877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
91977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
92077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
92177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
92277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (lastNonEmptyEmbeddingLevel == embeddingLevel) {
92377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_LTR;
92477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
92577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        --embeddingLevel;
92677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
92777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
92877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
92977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (embeddingLevel == 0) {
93077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_RTL;
93177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
93277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (lastNonEmptyEmbeddingLevel == 0) {
93377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            lastNonEmptyEmbeddingLevel = embeddingLevel;
93477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
93577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
93677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
93777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
93877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (lastNonEmptyEmbeddingLevel == embeddingLevel) {
93977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_RTL;
94077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
94177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        --embeddingLevel;
94277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
94377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT:
94477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        ++embeddingLevel;
94577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
94677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_BOUNDARY_NEUTRAL:
94777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
94877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    default:
94977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (lastNonEmptyEmbeddingLevel == 0) {
95077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            lastNonEmptyEmbeddingLevel = embeddingLevel;
95177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
95277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
95377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
95477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
95577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return DIR_UNKNOWN;
95677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
95777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
95877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        // Internal methods
95977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
96077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
96177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Gets the bidi character class, i.e. Character.getDirectionality(), of a given char, using
96277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * a cache for speed. Not designed for supplementary codepoints, whose results we do not
96377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * cache.
96477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
96577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private static byte getCachedDirectionality(char c) {
96677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return c < DIR_TYPE_CACHE_SIZE ? DIR_TYPE_CACHE[c] : Character.getDirectionality(c);
96777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
96877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
96977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
97077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Returns the Character.DIRECTIONALITY_... value of the next codepoint and advances
97177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * charIndex. If isHtml, and the codepoint is '<' or '&', advances through the tag/entity,
97277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * and returns Character.DIRECTIONALITY_WHITESPACE. For an entity, it would be best to
97377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * figure out the actual character, and return its dirtype, but treating it as whitespace is
97477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * good enough for our purposes.
97577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *
97677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @throws java.lang.IndexOutOfBoundsException if called when charIndex >= length or < 0.
97777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
97877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        byte dirTypeForward() {
97977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            lastChar = text.charAt(charIndex);
98077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (Character.isHighSurrogate(lastChar)) {
98177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                int codePoint = Character.codePointAt(text, charIndex);
98277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                charIndex += Character.charCount(codePoint);
98377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                return Character.getDirectionality(codePoint);
98477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
98577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex++;
98677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            byte dirType = getCachedDirectionality(lastChar);
98777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (isHtml) {
98877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                // Process tags and entities.
98977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '<') {
99077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    dirType = skipTagForward();
99177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                } else if (lastChar == '&') {
99277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    dirType = skipEntityForward();
99377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
99477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
99577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return dirType;
99677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
99777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
99877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
99977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Returns the Character.DIRECTIONALITY_... value of the preceding codepoint and advances
100077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * charIndex backwards. If isHtml, and the codepoint is the end of a complete HTML tag or
100177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * entity, advances over the whole tag/entity and returns
100277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Character.DIRECTIONALITY_WHITESPACE. For an entity, it would be best to figure out the
100377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * actual character, and return its dirtype, but treating it as whitespace is good enough
100477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * for our purposes.
100577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *
100677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @throws java.lang.IndexOutOfBoundsException if called when charIndex > length or <= 0.
100777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
100877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        byte dirTypeBackward() {
100977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            lastChar = text.charAt(charIndex - 1);
101077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (Character.isLowSurrogate(lastChar)) {
101177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                int codePoint = Character.codePointBefore(text, charIndex);
101277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                charIndex -= Character.charCount(codePoint);
101377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                return Character.getDirectionality(codePoint);
101477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
101577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex--;
101677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            byte dirType = getCachedDirectionality(lastChar);
101777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (isHtml) {
101877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                // Process tags and entities.
101977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '>') {
102077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    dirType = skipTagBackward();
102177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                } else if (lastChar == ';') {
102277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    dirType = skipEntityBackward();
102377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
102477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
102577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return dirType;
102677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
102777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
102877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
102977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Advances charIndex forward through an HTML tag (after the opening &lt; has already been
103077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * read) and returns Character.DIRECTIONALITY_WHITESPACE. If there is no matching &gt;,
103177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * does not change charIndex and returns Character.DIRECTIONALITY_OTHER_NEUTRALS (for the
103277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * &lt; that hadn't been part of a tag after all).
103377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
103477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private byte skipTagForward() {
103577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int initialCharIndex = charIndex;
103677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex < length) {
103777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                lastChar = text.charAt(charIndex++);
103877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '>') {
103977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    // The end of the tag.
104077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    return Character.DIRECTIONALITY_WHITESPACE;
104177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
104277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '"' || lastChar == '\'') {
104377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    // Skip over a quoted attribute value inside the tag.
104477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    char quote = lastChar;
104577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    while (charIndex < length && (lastChar = text.charAt(charIndex++)) != quote) {}
104677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
104777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
104877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // The original '<' wasn't the start of a tag after all.
104977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex = initialCharIndex;
105077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            lastChar = '<';
105177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return Character.DIRECTIONALITY_OTHER_NEUTRALS;
105277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
105377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
105477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
105577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Advances charIndex backward through an HTML tag (after the closing &gt; has already been
105677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * read) and returns Character.DIRECTIONALITY_WHITESPACE. If there is no matching &lt;, does
105777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * not change charIndex and returns Character.DIRECTIONALITY_OTHER_NEUTRALS (for the &gt;
105877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * that hadn't been part of a tag after all). Nevertheless, the running time for calling
105977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * skipTagBackward() in a loop remains linear in the size of the text, even for a text like
106077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * "&gt;&gt;&gt;&gt;", because skipTagBackward() also stops looking for a matching &lt;
106177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * when it encounters another &gt;.
106277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
106377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private byte skipTagBackward() {
106477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int initialCharIndex = charIndex;
106577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex > 0) {
106677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                lastChar = text.charAt(--charIndex);
106777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '<') {
106877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    // The start of the tag.
106977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    return Character.DIRECTIONALITY_WHITESPACE;
107077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
107177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '>') {
107277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    break;
107377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
107477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '"' || lastChar == '\'') {
107577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    // Skip over a quoted attribute value inside the tag.
107677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    char quote = lastChar;
107777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    while (charIndex > 0 && (lastChar = text.charAt(--charIndex)) != quote) {}
107877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
107977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
108077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // The original '>' wasn't the end of a tag after all.
108177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex = initialCharIndex;
108277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            lastChar = '>';
108377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return Character.DIRECTIONALITY_OTHER_NEUTRALS;
108477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
108577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
108677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
108777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Advances charIndex forward through an HTML character entity tag (after the opening
108877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * &amp; has already been read) and returns Character.DIRECTIONALITY_WHITESPACE. It would be
108977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * best to figure out the actual character and return its dirtype, but this is good enough.
109077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
109177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private byte skipEntityForward() {
109277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex < length && (lastChar = text.charAt(charIndex++)) != ';') {}
109377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return Character.DIRECTIONALITY_WHITESPACE;
109477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
109577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
109677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
109777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Advances charIndex backward through an HTML character entity tag (after the closing ;
109877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * has already been read) and returns Character.DIRECTIONALITY_WHITESPACE. It would be best
109977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * to figure out the actual character and return its dirtype, but this is good enough.
110077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * If there is no matching &amp;, does not change charIndex and returns
110177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Character.DIRECTIONALITY_OTHER_NEUTRALS (for the ';' that did not start an entity after
110277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * all). Nevertheless, the running time for calling skipEntityBackward() in a loop remains
110377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * linear in the size of the text, even for a text like ";;;;;;;", because skipTagBackward()
110477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * also stops looking for a matching &amp; when it encounters another ;.
110577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
110677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private byte skipEntityBackward() {
110777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int initialCharIndex = charIndex;
110877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex > 0) {
110977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                lastChar = text.charAt(--charIndex);
111077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '&') {
111177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    return Character.DIRECTIONALITY_WHITESPACE;
111277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
111377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == ';') {
111477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    break;
111577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
111677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
111777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex = initialCharIndex;
111877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            lastChar = ';';
111977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return Character.DIRECTIONALITY_OTHER_NEUTRALS;
112077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
112177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
112277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio}