BidiFormatter.java revision 57050da5a4c11fb1dd740599671540cc53b29cf7
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    /**
27557050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio     * Factory for creating an instance of BidiFormatter for the default locale directionality.
27657050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio     *
27757050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio     */
27857050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio    public static BidiFormatter getInstance() {
27957050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio        return new Builder().build();
28057050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio    }
28157050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio
28257050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio    /**
28377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Factory for creating an instance of BidiFormatter given the context directionality.
28477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
28577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param rtlContext Whether the context directionality is RTL.
28677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
28777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public static BidiFormatter getInstance(boolean rtlContext) {
28877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return new Builder(rtlContext).build();
28977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
29077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
29177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
29277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Factory for creating an instance of BidiFormatter given the context locale.
29377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
29477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param locale The context locale.
29577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
29677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public static BidiFormatter getInstance(Locale locale) {
29777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return new Builder(locale).build();
29877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
29977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
30077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
30177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param isRtlContext Whether the context directionality is RTL or not.
30277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param flags The option flags.
30377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The default text direction heuristic.
30477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
30577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private BidiFormatter(boolean isRtlContext, int flags, TextDirectionHeuristicCompat heuristic) {
306c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        mIsRtlContext = isRtlContext;
307c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        mFlags = flags;
308c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        mDefaultTextDirectionHeuristicCompat = heuristic;
30977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
31077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
31177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
31277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Whether the context directionality is RTL
31377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
31477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public boolean isRtlContext() {
315c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return mIsRtlContext;
31677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
31777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
31877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
31977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Whether directionality "reset" should also be done before a string being
32077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * bidi-wrapped, not just after it.
32177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
32277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public boolean getStereoReset() {
323c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return (mFlags & FLAG_STEREO_RESET) != 0;
32477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
32577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
32677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
32777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns "rtl" if {@code str}'s estimated directionality is RTL, and "ltr" if it is LTR.
32877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
32977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str String whose directionality is to be estimated.
33077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return "rtl" if {@code str}'s estimated directionality is RTL, and "ltr" otherwise.
33177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
33277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String dirAttrValue(String str) {
33377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return dirAttrValue(isRtl(str));
33477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
33577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
33677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
33777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #dirAttrValue(String)}, but uses a given heuristic to estimate the
33877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * {@code str}'s directionality.
33977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
34077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str String whose directionality is to be estimated.
34177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s
34277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *                  directionality.
34377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return "rtl" if {@code str}'s estimated directionality is RTL, and "ltr" otherwise.
34477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
34577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String dirAttrValue(String str, TextDirectionHeuristicCompat heuristic) {
34677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return dirAttrValue(heuristic.isRtl(str, 0, str.length()));
34777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
34877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
34977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
35077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns "rtl" if the given directionality is RTL, and "ltr" if it is LTR.
35177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
35277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param isRtl Whether the directionality is RTL or not.
35377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return "rtl" if the given directionality is RTL, and "ltr" otherwise.
35477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
35577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String dirAttrValue(boolean isRtl) {
35677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return isRtl ? RTL_STRING : LTR_STRING;
35777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
35877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
35977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
36077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns "dir=\"ltr\"" or "dir=\"rtl\"", depending on {@code str}'s estimated directionality,
36177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * if it is not the same as the context directionality. Otherwise, returns the empty string.
36277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
36377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str String whose directionality is to be estimated.
36477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return "dir=\"rtl\"" for RTL text in non-RTL context; "dir=\"ltr\"" for LTR text in non-LTR
36577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     context; else, the empty string.
36677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
36777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String dirAttr(String str) {
36877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return dirAttr(isRtl(str));
36977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
37077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
37177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
37277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #dirAttr(String)}, but uses a given heuristic to estimate the
37377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * {@code str}'s directionality.
37477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
37577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str String whose directionality is to be estimated.
37677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s
37777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *                  directionality.
37877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return "dir=\"rtl\"" for RTL text in non-RTL context; "dir=\"ltr\"" for LTR text in non-LTR
37977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     context; else, the empty string.
38077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
38177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String dirAttr(String str, TextDirectionHeuristicCompat heuristic) {
38277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return dirAttr(heuristic.isRtl(str, 0, str.length()));
38377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
38477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
38577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
38677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns "dir=\"ltr\"" or "dir=\"rtl\"", depending on the given directionality, if it is not
38777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * the same as the context directionality. Otherwise, returns the empty string.
38877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
38977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param isRtl Whether the directionality is RTL or not
39077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return "dir=\"rtl\"" for RTL text in non-RTL context; "dir=\"ltr\"" for LTR text in non-LTR
39177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     context; else, the empty string.
39277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
39377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String dirAttr(boolean isRtl) {
394c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return (isRtl != mIsRtlContext) ? (isRtl ? DIR_RTL_STRING :  DIR_LTR_STRING) : EMPTY_STRING;
39577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
39677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
39777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
39877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns a Unicode bidi mark matching the context directionality (LRM or RLM) if either the
39977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * overall or the exit directionality of a given string is opposite to the context directionality.
40077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Putting this after the string (including its directionality declaration wrapping) prevents it
40177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * from "sticking" to other opposite-directionality text or a number appearing after it inline
40277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * with only neutral content in between. Otherwise returns the empty string. While the exit
40377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * directionality is determined by scanning the end of the string, the overall directionality is
40477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * given explicitly in {@code dir}.
40577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
40677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str String after which the mark may need to appear.
40777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context;
40877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     else, the empty string.
40977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
41077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String markAfter(String str) {
411c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return markAfter(str, mDefaultTextDirectionHeuristicCompat);
41277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
41377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
41477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
41577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #markAfter(String)}, but uses a given heuristic to estimate the
41677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * {@code str}'s directionality.
41777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
41877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str String after which the mark may need to appear.
41977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s
42077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *                  directionality.
42177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context;
42277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     else, the empty string.
42377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
42477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String markAfter(String str, TextDirectionHeuristicCompat heuristic) {
42577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        final boolean isRtl = heuristic.isRtl(str, 0, str.length());
42677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        // getExitDir() is called only if needed (short-circuit).
427c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        if (!mIsRtlContext && (isRtl || getExitDir(str) == DIR_RTL)) {
42877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return LRM_STRING;
42977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
430c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        if (mIsRtlContext && (!isRtl || getExitDir(str) == DIR_LTR)) {
43177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return RLM_STRING;
43277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
43377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return EMPTY_STRING;
43477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
43577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
43677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
43777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns a Unicode bidi mark matching the context directionality (LRM or RLM) if either the
43877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * overall or the entry directionality of a given string is opposite to the context
43977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * directionality. Putting this before the string (including its directionality declaration
44077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * wrapping) prevents it from "sticking" to other opposite-directionality text appearing before it
44177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * inline with only neutral content in between. Otherwise returns the empty string. While the
44277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * entry directionality is determined by scanning the beginning of the string, the overall
44377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * directionality is given explicitly in {@code dir}.
44477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
44577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str String before which the mark may need to appear.
44677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context;
44777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     else, the empty string.
44877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
44977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String markBefore(String str) {
450c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return markBefore(str, mDefaultTextDirectionHeuristicCompat);
45177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
45277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
45377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
45477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #markBefore(String)}, but uses a given heuristic to estimate the
45577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * {@code str}'s directionality.
45677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
45777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str String before which the mark may need to appear.
45877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s
45977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *                  directionality.
46077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context;
46177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     else, the empty string.
46277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
46377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String markBefore(String str, TextDirectionHeuristicCompat heuristic) {
46477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        final boolean isRtl = heuristic.isRtl(str, 0, str.length());
46577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        // getEntryDir() is called only if needed (short-circuit).
466c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        if (!mIsRtlContext && (isRtl || getEntryDir(str) == DIR_RTL)) {
46777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return LRM_STRING;
46877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
469c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        if (mIsRtlContext && (!isRtl || getEntryDir(str) == DIR_LTR)) {
47077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return RLM_STRING;
47177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
47277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return EMPTY_STRING;
47377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
47477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
47577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
47677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns the Unicode bidi mark matching the context directionality (LRM for LTR context
47777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * directionality, RLM for RTL context directionality).
47877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
47977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String mark() {
480c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return mIsRtlContext ? RLM_STRING : LRM_STRING;
48177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
48277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
48377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
48477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns "right" for RTL context directionality. Otherwise for LTR context directionality
48577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * returns "left".
48677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
48777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String startEdge() {
488c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return mIsRtlContext ? RIGHT : LEFT;
48977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
49077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
49177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
49277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns "left" for RTL context directionality. Otherwise for LTR context directionality
49377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * returns "right".
49477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
49577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String endEdge() {
496c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return mIsRtlContext ? LEFT : RIGHT;
49777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
49877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
49977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
50077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Estimates the directionality of a string using the default text direction heuristic.
50177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
50277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str String whose directionality is to be estimated.
50377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return true if {@code str}'s estimated overall directionality is RTL. Otherwise returns
50477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *          false.
50577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
50677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public boolean isRtl(String str) {
507c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return mDefaultTextDirectionHeuristicCompat.isRtl(str, 0, str.length());
50877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
50977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
51077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
51177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Formats a given string of unknown directionality for use in HTML output of the context
51277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * directionality, so an opposite-directionality string is neither garbled nor garbles its
51377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * surroundings.
51477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * <p>
51577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * The algorithm: estimates the directionality of the given string using the given heuristic.
51677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * If the directionality is known, pass TextDirectionHeuristics.LTR or RTL for heuristic.
51777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * In case its directionality doesn't match the context directionality, wraps it with a 'span'
51877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * element and adds a "dir" attribute (either 'dir=\"rtl\"' or 'dir=\"ltr\"').
51977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * <p>
52077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * If {@code isolate}, directionally isolates the string so that it does not garble its
52177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * surroundings. Currently, this is done by "resetting" the directionality after the string by
52277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * appending a trailing Unicode bidi mark matching the context directionality (LRM or RLM) when
52377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * either the overall directionality or the exit directionality of the string is opposite to that
52477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * of the context. If the formatter was built using {@link Builder#stereoReset(boolean)} and
52577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * passing "true" as an argument, also prepends a Unicode bidi mark matching the context
52677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * directionality when either the overall directionality or the entry directionality of the
52777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * string is opposite to that of the context.
52877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * <p>
52977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
53077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
53177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The algorithm to be used to estimate the string's overall direction.
53277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param isolate Whether to directionally isolate the string to prevent it from garbling the
53377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     content around it.
53477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Input string after applying the above processing.
53577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
53677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String spanWrap(String str, TextDirectionHeuristicCompat heuristic, boolean isolate) {
53777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        final boolean isRtl = heuristic.isRtl(str, 0, str.length());
53877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        String origStr = str;
53977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        str = TextUtilsCompat.htmlEncode(str);
54077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
54177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        StringBuilder result = new StringBuilder();
54277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        if (getStereoReset() && isolate) {
54377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(markBefore(origStr,
54477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    isRtl ? TextDirectionHeuristicsCompat.RTL : TextDirectionHeuristicsCompat.LTR));
54577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
546c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        if (isRtl != mIsRtlContext) {
54777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append("<span ").append(dirAttr(isRtl)).append('>').append(str).append("</span>");
54877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        } else {
54977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(str);
55077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
55177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        if (isolate) {
55277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(markAfter(origStr,
55377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    isRtl ? TextDirectionHeuristicsCompat.RTL : TextDirectionHeuristicsCompat.LTR));
55477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
55577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return result.toString();
55677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
55777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
55877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
55977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #spanWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but assumes
56077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * {@code isolate} is true.
56177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
56277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
56377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The algorithm to be used to estimate the string's overall direction.
56477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Input string after applying the above processing.
56577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
56677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String spanWrap(String str, TextDirectionHeuristicCompat heuristic) {
56777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return spanWrap(str, heuristic, true /* isolate */);
56877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
56977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
57077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
57177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #spanWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but uses the
57277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * formatter's default direction estimation algorithm.
57377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
57477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
57577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param isolate Whether to directionally isolate the string to prevent it from garbling the
57677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     content around it
57777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Input string after applying the above processing.
57877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
57977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String spanWrap(String str, boolean isolate) {
580c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return spanWrap(str, mDefaultTextDirectionHeuristicCompat, isolate);
58177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
58277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
58377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
58477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #spanWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but uses the
58577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * formatter's default direction estimation algorithm and assumes {@code isolate} is true.
58677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
58777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
58877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Input string after applying the above processing.
58977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
59077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String spanWrap(String str) {
591c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return spanWrap(str, mDefaultTextDirectionHeuristicCompat, true /* isolate */);
59277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
59377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
59477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
59577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Formats a string of given directionality for use in plain-text output of the context
59677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * directionality, so an opposite-directionality string is neither garbled nor garbles its
59777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * surroundings. As opposed to {@link #spanWrap}, this makes use of Unicode bidi
59877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * formatting characters. In HTML, its *only* valid use is inside of elements that do not allow
59977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * markup, e.g. the 'option' and 'title' elements.
60077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * <p>
60177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * The algorithm: In case the given directionality doesn't match the context directionality, wraps
60277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * the string with Unicode bidi formatting characters: RLE+{@code str}+PDF for RTL text, or
60377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * LRE+{@code str}+PDF for LTR text.
60477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * <p>
60577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * If {@code isolate}, directionally isolates the string so that it does not garble its
60677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * surroundings. Currently, this is done by "resetting" the directionality after the string by
60777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * appending a trailing Unicode bidi mark matching the context directionality (LRM or RLM) when
60877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * either the overall directionality or the exit directionality of the string is opposite to that
60977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * of the context. If the formatter was built using {@link Builder#stereoReset(boolean)} and
61077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * passing "true" as an argument, also prepends a Unicode bidi mark matching the context
61177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * directionality when either the overall directionality or the entry directionality of the
61277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * string is opposite to that of the context. Note that as opposed to the overall
61377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * directionality, the entry and exit directionalities are determined from the string itself.
61477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * <p>
61577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Does *not* do HTML-escaping.
61677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
61777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
61877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The algorithm to be used to estimate the string's overall direction.
61977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param isolate Whether to directionally isolate the string to prevent it from garbling the
62077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     content around it
62177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Input string after applying the above processing.
62277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
62377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String unicodeWrap(String str, TextDirectionHeuristicCompat heuristic, boolean isolate) {
62477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        final boolean isRtl = heuristic.isRtl(str, 0, str.length());
62577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        StringBuilder result = new StringBuilder();
62677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        if (getStereoReset() && isolate) {
62777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(markBefore(str,
62877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    isRtl ? TextDirectionHeuristicsCompat.RTL : TextDirectionHeuristicsCompat.LTR));
62977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
630c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        if (isRtl != mIsRtlContext) {
63177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(isRtl ? RLE : LRE);
63277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(str);
63377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(PDF);
63477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        } else {
63577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(str);
63677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
63777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        if (isolate) {
63877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(markAfter(str,
63977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    isRtl ? TextDirectionHeuristicsCompat.RTL : TextDirectionHeuristicsCompat.LTR));
64077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
64177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return result.toString();
64277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
64377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
64477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
64577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #unicodeWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but assumes
64677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * {@code isolate} is true.
64777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
64877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
64977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The algorithm to be used to estimate the string's overall direction.
65077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Input string after applying the above processing.
65177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
65277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String unicodeWrap(String str, TextDirectionHeuristicCompat heuristic) {
65377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return unicodeWrap(str, heuristic, true /* isolate */);
65477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
65577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
65677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
65777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #unicodeWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but uses the
65877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * formatter's default direction estimation algorithm.
65977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
66077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
66177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param isolate Whether to directionally isolate the string to prevent it from garbling the
66277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     content around it
66377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Input string after applying the above processing.
66477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
66577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String unicodeWrap(String str, boolean isolate) {
666c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return unicodeWrap(str, mDefaultTextDirectionHeuristicCompat, isolate);
66777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
66877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
66977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
67077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #unicodeWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but uses the
67177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * formatter's default direction estimation algorithm and assumes {@code isolate} is true.
67277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
67377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
67477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Input string after applying the above processing.
67577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
67677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String unicodeWrap(String str) {
677c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return unicodeWrap(str, mDefaultTextDirectionHeuristicCompat, true /* isolate */);
67877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
67977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
68077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
68177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Helper method to return true if the Locale directionality is RTL.
68277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
68377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param locale The Locale whose directionality will be checked to be RTL or LTR
68477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return true if the {@code locale} directionality is RTL. False otherwise.
68577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
68677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static boolean isRtlLocale(Locale locale) {
68777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return (TextUtilsCompat.getLayoutDirectionFromLocale(locale) == ViewCompat.LAYOUT_DIRECTION_RTL);
68877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
68977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
69077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
69177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Enum for directionality type.
69277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
69377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final int DIR_LTR = -1;
69477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final int DIR_UNKNOWN = 0;
69577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final int DIR_RTL = +1;
69677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
69777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
69877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns the directionality of the last character with strong directionality in the string, or
69977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * DIR_UNKNOWN if none was encountered. For efficiency, actually scans backwards from the end of
70077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * the string. Treats a non-BN character between an LRE/RLE/LRO/RLO and its matching PDF as a
70177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * strong character, LTR after LRE/LRO, and RTL after RLE/RLO. The results are undefined for a
70277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * string containing unbalanced LRE/RLE/LRO/RLO/PDF characters. The intended use is to check
70377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * whether a logically separate item that starts with a number or a character of the string's
70477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * exit directionality and follows this string inline (not counting any neutral characters in
70577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * between) would "stick" to it in an opposite-directionality context, thus being displayed in
70677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * an incorrect position. An LRM or RLM character (the one of the context's directionality)
70777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * between the two will prevent such sticking.
70877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
70977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str the string to check.
71077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
71177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static int getExitDir(String str) {
71277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return new DirectionalityEstimator(str, false /* isHtml */).getExitDir();
71377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
71477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
71577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
71677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns the directionality of the first character with strong directionality in the string,
71777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * or DIR_UNKNOWN if none was encountered. Treats a non-BN character between an
71877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * LRE/RLE/LRO/RLO and its matching PDF as a strong character, LTR after LRE/LRO, and RTL after
71977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * RLE/RLO. The results are undefined for a string containing unbalanced LRE/RLE/LRO/RLO/PDF
72077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * characters. The intended use is to check whether a logically separate item that ends with a
72177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * character of the string's entry directionality and precedes the string inline (not counting
72277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * any neutral characters in between) would "stick" to it in an opposite-directionality context,
72377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * thus being displayed in an incorrect position. An LRM or RLM character (the one of the
72477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * context's directionality) between the two will prevent such sticking.
72577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
72677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str the string to check.
72777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
72877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static int getEntryDir(String str) {
72977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return new DirectionalityEstimator(str, false /* isHtml */).getEntryDir();
73077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
73177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
73277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
73377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * An object that estimates the directionality of a given string by various methods.
73477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
73577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
73677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static class DirectionalityEstimator {
73777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
73877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        // Internal static variables and constants.
73977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
74077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
74177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Size of the bidi character class cache. The results of the Character.getDirectionality()
74277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * calls on the lowest DIR_TYPE_CACHE_SIZE codepoints are kept in an array for speed.
74377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The 0x700 value is designed to leave all the European and Near Eastern languages in the
74477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * cache. It can be reduced to 0x180, restricting the cache to the Western European
74577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * languages.
74677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
74777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private static final int DIR_TYPE_CACHE_SIZE = 0x700;
74877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
74977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
75077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The bidi character class cache.
75177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
75277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private static final byte DIR_TYPE_CACHE[];
75377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
75477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        static {
75577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            DIR_TYPE_CACHE = new byte[DIR_TYPE_CACHE_SIZE];
75677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            for (int i = 0; i < DIR_TYPE_CACHE_SIZE; i++) {
75777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                DIR_TYPE_CACHE[i] = Character.getDirectionality(i);
75877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
75977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
76077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
76177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        // Internal instance variables.
76277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
76377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
76477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The text to be scanned.
76577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
76677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private final String text;
76777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
76877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
76977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Whether the text to be scanned is to be treated as HTML, i.e. skipping over tags and
77077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * entities when looking for the next / preceding dir type.
77177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
77277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private final boolean isHtml;
77377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
77477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
77577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The length of the text in chars.
77677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
77777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private final int length;
77877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
77977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
78077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The current position in the text.
78177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
78277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private int charIndex;
78377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
78477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
78577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The char encountered by the last dirTypeForward or dirTypeBackward call. If it
78677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * encountered a supplementary codepoint, this contains a char that is not a valid
78777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * codepoint. This is ok, because this member is only used to detect some well-known ASCII
78877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * syntax, e.g. "http://" and the beginning of an HTML tag or entity.
78977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
79077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private char lastChar;
79177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
79277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
79377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Constructor.
79477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *
79577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @param text The string to scan.
79677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @param isHtml Whether the text to be scanned is to be treated as HTML, i.e. skipping over
79777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *     tags and entities.
79877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
79977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        DirectionalityEstimator(String text, boolean isHtml) {
80077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            this.text = text;
80177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            this.isHtml = isHtml;
80277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            length = text.length();
80377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
80477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
80577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
80677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Returns the directionality of the first character with strong directionality in the
80777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * string, or DIR_UNKNOWN if none was encountered. Treats a non-BN character between an
80877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * LRE/RLE/LRO/RLO and its matching PDF as a strong character, LTR after LRE/LRO, and RTL
80977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * after RLE/RLO. The results are undefined for a string containing unbalanced
81077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * LRE/RLE/LRO/RLO/PDF characters.
81177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
81277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        int getEntryDir() {
81377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // The reason for this method name, as opposed to getFirstStrongDir(), is that
81477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // "first strong" is a commonly used description of Unicode's estimation algorithm,
81577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // but the two must treat formatting characters quite differently. Thus, we are staying
81677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // away from both "first" and "last" in these method names to avoid confusion.
81777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex = 0;
81877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int embeddingLevel = 0;
81977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int embeddingLevelDir = DIR_UNKNOWN;
82077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int firstNonEmptyEmbeddingLevel = 0;
82177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex < length && firstNonEmptyEmbeddingLevel == 0) {
82277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                switch (dirTypeForward()) {
82377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
82477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
82577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        ++embeddingLevel;
82677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        embeddingLevelDir = DIR_LTR;
82777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
82877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
82977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
83077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        ++embeddingLevel;
83177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        embeddingLevelDir = DIR_RTL;
83277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
83377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT:
83477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        --embeddingLevel;
83577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        // To restore embeddingLevelDir to its previous value, we would need a
83677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        // stack, which we want to avoid. Thus, at this point we do not know the
83777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        // current embedding's directionality.
83877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        embeddingLevelDir = DIR_UNKNOWN;
83977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
84077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_BOUNDARY_NEUTRAL:
84177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
84277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
84377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (embeddingLevel == 0) {
84477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_LTR;
84577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
84677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        firstNonEmptyEmbeddingLevel = embeddingLevel;
84777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
84877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
84977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
85077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (embeddingLevel == 0) {
85177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_RTL;
85277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
85377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        firstNonEmptyEmbeddingLevel = embeddingLevel;
85477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
85577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    default:
85677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        firstNonEmptyEmbeddingLevel = embeddingLevel;
85777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
85877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
85977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
86077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
86177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // We have either found a non-empty embedding or scanned the entire string finding
86277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // neither a non-empty embedding nor a strong character outside of an embedding.
86377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (firstNonEmptyEmbeddingLevel == 0) {
86477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                // We have not found a non-empty embedding. Thus, the string contains neither a
86577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                // non-empty embedding nor a strong character outside of an embedding.
86677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                return DIR_UNKNOWN;
86777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
86877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
86977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // We have found a non-empty embedding.
87077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (embeddingLevelDir != DIR_UNKNOWN) {
87177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                // We know the directionality of the non-empty embedding.
87277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                return embeddingLevelDir;
87377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
87477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
87577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // We do not remember the directionality of the non-empty embedding we found. So, we go
87677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // backwards to find the start of the non-empty embedding and get its directionality.
87777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex > 0) {
87877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                switch (dirTypeBackward()) {
87977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
88077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
88177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (firstNonEmptyEmbeddingLevel == embeddingLevel) {
88277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_LTR;
88377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
88477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        --embeddingLevel;
88577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
88677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
88777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
88877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (firstNonEmptyEmbeddingLevel == embeddingLevel) {
88977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_RTL;
89077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
89177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        --embeddingLevel;
89277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
89377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT:
89477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        ++embeddingLevel;
89577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
89677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
89777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
89877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // We should never get here.
89977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return DIR_UNKNOWN;
90077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
90177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
90277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
90377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Returns the directionality of the last character with strong directionality in the
90477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * string, or DIR_UNKNOWN if none was encountered. For efficiency, actually scans backwards
90577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * from the end of the string. Treats a non-BN character between an LRE/RLE/LRO/RLO and its
90677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * matching PDF as a strong character, LTR after LRE/LRO, and RTL after RLE/RLO. The results
90777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * are undefined for a string containing unbalanced LRE/RLE/LRO/RLO/PDF characters.
90877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
90977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        int getExitDir() {
91077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // The reason for this method name, as opposed to getLastStrongDir(), is that "last
91177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // strong" sounds like the exact opposite of "first strong", which is a commonly used
91277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // description of Unicode's estimation algorithm (getUnicodeDir() above), but the two
91377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // must treat formatting characters quite differently. Thus, we are staying away from
91477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // both "first" and "last" in these method names to avoid confusion.
91577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex = length;
91677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int embeddingLevel = 0;
91777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int lastNonEmptyEmbeddingLevel = 0;
91877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex > 0) {
91977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                switch (dirTypeBackward()) {
92077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
92177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (embeddingLevel == 0) {
92277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_LTR;
92377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
92477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (lastNonEmptyEmbeddingLevel == 0) {
92577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            lastNonEmptyEmbeddingLevel = embeddingLevel;
92677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
92777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
92877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
92977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
93077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (lastNonEmptyEmbeddingLevel == embeddingLevel) {
93177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_LTR;
93277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
93377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        --embeddingLevel;
93477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
93577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
93677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
93777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (embeddingLevel == 0) {
93877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_RTL;
93977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
94077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (lastNonEmptyEmbeddingLevel == 0) {
94177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            lastNonEmptyEmbeddingLevel = embeddingLevel;
94277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
94377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
94477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
94577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
94677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (lastNonEmptyEmbeddingLevel == embeddingLevel) {
94777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_RTL;
94877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
94977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        --embeddingLevel;
95077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
95177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT:
95277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        ++embeddingLevel;
95377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
95477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_BOUNDARY_NEUTRAL:
95577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
95677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    default:
95777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (lastNonEmptyEmbeddingLevel == 0) {
95877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            lastNonEmptyEmbeddingLevel = embeddingLevel;
95977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
96077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
96177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
96277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
96377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return DIR_UNKNOWN;
96477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
96577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
96677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        // Internal methods
96777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
96877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
96977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Gets the bidi character class, i.e. Character.getDirectionality(), of a given char, using
97077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * a cache for speed. Not designed for supplementary codepoints, whose results we do not
97177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * cache.
97277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
97377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private static byte getCachedDirectionality(char c) {
97477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return c < DIR_TYPE_CACHE_SIZE ? DIR_TYPE_CACHE[c] : Character.getDirectionality(c);
97577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
97677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
97777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
97877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Returns the Character.DIRECTIONALITY_... value of the next codepoint and advances
97977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * charIndex. If isHtml, and the codepoint is '<' or '&', advances through the tag/entity,
98077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * and returns Character.DIRECTIONALITY_WHITESPACE. For an entity, it would be best to
98177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * figure out the actual character, and return its dirtype, but treating it as whitespace is
98277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * good enough for our purposes.
98377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *
98477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @throws java.lang.IndexOutOfBoundsException if called when charIndex >= length or < 0.
98577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
98677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        byte dirTypeForward() {
98777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            lastChar = text.charAt(charIndex);
98877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (Character.isHighSurrogate(lastChar)) {
98977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                int codePoint = Character.codePointAt(text, charIndex);
99077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                charIndex += Character.charCount(codePoint);
99177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                return Character.getDirectionality(codePoint);
99277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
99377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex++;
99477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            byte dirType = getCachedDirectionality(lastChar);
99577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (isHtml) {
99677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                // Process tags and entities.
99777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '<') {
99877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    dirType = skipTagForward();
99977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                } else if (lastChar == '&') {
100077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    dirType = skipEntityForward();
100177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
100277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
100377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return dirType;
100477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
100577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
100677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
100777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Returns the Character.DIRECTIONALITY_... value of the preceding codepoint and advances
100877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * charIndex backwards. If isHtml, and the codepoint is the end of a complete HTML tag or
100977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * entity, advances over the whole tag/entity and returns
101077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Character.DIRECTIONALITY_WHITESPACE. For an entity, it would be best to figure out the
101177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * actual character, and return its dirtype, but treating it as whitespace is good enough
101277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * for our purposes.
101377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *
101477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @throws java.lang.IndexOutOfBoundsException if called when charIndex > length or <= 0.
101577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
101677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        byte dirTypeBackward() {
101777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            lastChar = text.charAt(charIndex - 1);
101877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (Character.isLowSurrogate(lastChar)) {
101977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                int codePoint = Character.codePointBefore(text, charIndex);
102077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                charIndex -= Character.charCount(codePoint);
102177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                return Character.getDirectionality(codePoint);
102277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
102377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex--;
102477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            byte dirType = getCachedDirectionality(lastChar);
102577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (isHtml) {
102677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                // Process tags and entities.
102777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '>') {
102877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    dirType = skipTagBackward();
102977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                } else if (lastChar == ';') {
103077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    dirType = skipEntityBackward();
103177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
103277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
103377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return dirType;
103477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
103577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
103677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
103777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Advances charIndex forward through an HTML tag (after the opening &lt; has already been
103877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * read) and returns Character.DIRECTIONALITY_WHITESPACE. If there is no matching &gt;,
103977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * does not change charIndex and returns Character.DIRECTIONALITY_OTHER_NEUTRALS (for the
104077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * &lt; that hadn't been part of a tag after all).
104177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
104277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private byte skipTagForward() {
104377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int initialCharIndex = charIndex;
104477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex < length) {
104577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                lastChar = text.charAt(charIndex++);
104677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '>') {
104777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    // The end of the tag.
104877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    return Character.DIRECTIONALITY_WHITESPACE;
104977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
105077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '"' || lastChar == '\'') {
105177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    // Skip over a quoted attribute value inside the tag.
105277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    char quote = lastChar;
105377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    while (charIndex < length && (lastChar = text.charAt(charIndex++)) != quote) {}
105477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
105577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
105677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // The original '<' wasn't the start of a tag after all.
105777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex = initialCharIndex;
105877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            lastChar = '<';
105977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return Character.DIRECTIONALITY_OTHER_NEUTRALS;
106077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
106177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
106277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
106377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Advances charIndex backward through an HTML tag (after the closing &gt; has already been
106477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * read) and returns Character.DIRECTIONALITY_WHITESPACE. If there is no matching &lt;, does
106577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * not change charIndex and returns Character.DIRECTIONALITY_OTHER_NEUTRALS (for the &gt;
106677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * that hadn't been part of a tag after all). Nevertheless, the running time for calling
106777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * skipTagBackward() in a loop remains linear in the size of the text, even for a text like
106877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * "&gt;&gt;&gt;&gt;", because skipTagBackward() also stops looking for a matching &lt;
106977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * when it encounters another &gt;.
107077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
107177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private byte skipTagBackward() {
107277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int initialCharIndex = charIndex;
107377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex > 0) {
107477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                lastChar = text.charAt(--charIndex);
107577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '<') {
107677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    // The start of the tag.
107777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    return Character.DIRECTIONALITY_WHITESPACE;
107877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
107977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '>') {
108077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    break;
108177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
108277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '"' || lastChar == '\'') {
108377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    // Skip over a quoted attribute value inside the tag.
108477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    char quote = lastChar;
108577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    while (charIndex > 0 && (lastChar = text.charAt(--charIndex)) != quote) {}
108677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
108777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
108877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // The original '>' wasn't the end of a tag after all.
108977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex = initialCharIndex;
109077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            lastChar = '>';
109177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return Character.DIRECTIONALITY_OTHER_NEUTRALS;
109277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
109377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
109477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
109577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Advances charIndex forward through an HTML character entity tag (after the opening
109677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * &amp; has already been read) and returns Character.DIRECTIONALITY_WHITESPACE. It would be
109777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * best to figure out the actual character and return its dirtype, but this is good enough.
109877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
109977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private byte skipEntityForward() {
110077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex < length && (lastChar = text.charAt(charIndex++)) != ';') {}
110177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return Character.DIRECTIONALITY_WHITESPACE;
110277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
110377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
110477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
110577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Advances charIndex backward through an HTML character entity tag (after the closing ;
110677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * has already been read) and returns Character.DIRECTIONALITY_WHITESPACE. It would be best
110777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * to figure out the actual character and return its dirtype, but this is good enough.
110877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * If there is no matching &amp;, does not change charIndex and returns
110977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Character.DIRECTIONALITY_OTHER_NEUTRALS (for the ';' that did not start an entity after
111077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * all). Nevertheless, the running time for calling skipEntityBackward() in a loop remains
111177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * linear in the size of the text, even for a text like ";;;;;;;", because skipTagBackward()
111277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * also stops looking for a matching &amp; when it encounters another ;.
111377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
111477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private byte skipEntityBackward() {
111577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int initialCharIndex = charIndex;
111677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex > 0) {
111777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                lastChar = text.charAt(--charIndex);
111877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '&') {
111977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    return Character.DIRECTIONALITY_WHITESPACE;
112077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
112177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == ';') {
112277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    break;
112377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
112477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
112577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex = initialCharIndex;
112677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            lastChar = ';';
112777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return Character.DIRECTIONALITY_OTHER_NEUTRALS;
112877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
112977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
113077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio}