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
17b37703fef07bb1940fd9d933bb9cc5b6c3959cb2Fabrice Di Megliopackage android.support.v4.text;
1877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
1977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglioimport android.support.v4.view.ViewCompat;
2077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
2177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglioimport java.util.Locale;
2277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
2377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglioimport static android.support.v4.text.TextDirectionHeuristicsCompat.FIRSTSTRONG_LTR;
2477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
2577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio/**
2677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Utility class for formatting text for display in a potentially opposite-directionality context
2777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * without garbling. The directionality of the context is set at formatter creation and the
2877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality of the text can be either estimated or passed in when known. Provides the
2977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * following functionality:
3077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p>
3177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 1. Bidi Wrapping
3277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * When text in one language is mixed into a document in another, opposite-directionality language,
3377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * e.g. when an English business name is embedded in a Hebrew web page, both the inserted string
3477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * and the text surrounding it may be displayed incorrectly unless the inserted string is explicitly
3577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * separated from the surrounding text in a "wrapper" that:
3677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p>
37b37703fef07bb1940fd9d933bb9cc5b6c3959cb2Fabrice Di Meglio * - Declares its directionality so that the string is displayed correctly. This can be done in
38b37703fef07bb1940fd9d933bb9cc5b6c3959cb2Fabrice Di Meglio *   Unicode bidi formatting codes by {@link #unicodeWrap} and similar methods.
3977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p>
4077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * - Isolates the string's directionality, so it does not unduly affect the surrounding content.
4177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   Currently, this can only be done using invisible Unicode characters of the same direction as
4277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   the context (LRM or RLM) in addition to the directionality declaration above, thus "resetting"
4377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   the directionality to that of the context. The "reset" may need to be done at both ends of the
4477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   string. Without "reset" after the string, the string will "stick" to a number or logically
4577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   separate opposite-direction text that happens to follow it in-line (even if separated by
4677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   neutral content like spaces and punctuation). Without "reset" before the string, the same can
4777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   happen there, but only with more opposite-direction text, not a number. One approach is to
4877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   "reset" the direction only after each string, on the theory that if the preceding opposite-
4977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   direction text is itself bidi-wrapped, the "reset" after it will prevent the sticking. (Doing
5077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   the "reset" only before each string definitely does not work because we do not want to require
5177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   bidi-wrapping numbers, and a bidi-wrapped opposite-direction string could be followed by a
5277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   number.) Still, the safest policy is to do the "reset" on both ends of each string, since RTL
5377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   message translations often contain untranslated Latin-script brand names and technical terms,
5477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   and one of these can be followed by a bidi-wrapped inserted value. On the other hand, when one
5577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   has such a message, it is best to do the "reset" manually in the message translation itself,
5677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   since the message's opposite-direction text could be followed by an inserted number, which we
5777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   would not bidi-wrap anyway. Thus, "reset" only after the string is the current default. In an
5877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   alternative to "reset", recent additions to the HTML, CSS, and Unicode standards allow the
5977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   isolation to be part of the directionality declaration. This form of isolation is better than
6077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   "reset" because it takes less space, does not require knowing the context directionality, has a
6177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   gentler effect than "reset", and protects both ends of the string. However, we do not yet allow
6277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio *   using it because required platforms do not yet support it.
6377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p>
6477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Providing these wrapping services is the basic purpose of the bidi formatter.
6577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p>
6677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 2. Directionality estimation
6777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * How does one know whether a string about to be inserted into surrounding text has the same
6877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality? Well, in many cases, one knows that this must be the case when writing the code
6977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * doing the insertion, e.g. when a localized message is inserted into a localized page. In such
7077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * cases there is no need to involve the bidi formatter at all. In some other cases, it need not be
7177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * the same as the context, but is either constant (e.g. urls are always LTR) or otherwise known.
7277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * In the remaining cases, e.g. when the string is user-entered or comes from a database, the
7377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * language of the string (and thus its directionality) is not known a priori, and must be
7477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * estimated at run-time. The bidi formatter can do this automatically using the default
7577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * first-strong estimation algorithm. It can also be configured to use a custom directionality
7677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * estimation object.
7777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */
7877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Megliopublic final class BidiFormatter {
7977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
8077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
8177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * The default text direction heuristic.
8277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
8377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static TextDirectionHeuristicCompat DEFAULT_TEXT_DIRECTION_HEURISTIC = FIRSTSTRONG_LTR;
8477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
8577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
8677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Unicode "Left-To-Right Embedding" (LRE) character.
8777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
8877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final char LRE = '\u202A';
8977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
9077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
9177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Unicode "Right-To-Left Embedding" (RLE) character.
9277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
9377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final char RLE = '\u202B';
9477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
9577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
9677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Unicode "Pop Directional Formatting" (PDF) character.
9777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
9877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final char PDF = '\u202C';
9977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
10077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
10177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *  Unicode "Left-To-Right Mark" (LRM) character.
10277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
10377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final char LRM = '\u200E';
10477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
10577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /*
10677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Unicode "Right-To-Left Mark" (RLM) character.
10777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
10877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final char RLM = '\u200F';
10977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
11077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /*
11177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * String representation of LRM
11277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
11377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final String LRM_STRING = Character.toString(LRM);
11477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
11577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /*
11677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * String representation of RLM
11777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
11877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final String RLM_STRING = Character.toString(RLM);
11977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
12077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
12177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Empty string constant.
12277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
12377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final String EMPTY_STRING = "";
12477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
12577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
12677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * A class for building a BidiFormatter with non-default options.
12777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
12877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public static final class Builder {
129c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        private boolean mIsRtlContext;
130c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        private int mFlags;
131c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        private TextDirectionHeuristicCompat mTextDirectionHeuristicCompat;
13277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
13377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
13477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Constructor.
13577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *
13677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
13777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        public Builder() {
13877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            initialize(isRtlLocale(Locale.getDefault()));
13977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
14077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
14177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
14277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Constructor.
14377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *
14477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @param rtlContext Whether the context directionality is RTL.
14577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
14677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        public Builder(boolean rtlContext) {
14777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            initialize(rtlContext);
14877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
14977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
15077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
15177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Constructor.
15277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *
15377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @param locale The context locale.
15477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
15577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        public Builder(Locale locale) {
15677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            initialize(isRtlLocale(locale));
15777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
15877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
15977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
16077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Initializes the builder with the given context directionality and default options.
16177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *
16277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @param isRtlContext Whether the context is RTL or not.
16377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
16477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private void initialize(boolean isRtlContext) {
165c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio            mIsRtlContext = isRtlContext;
166c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio            mTextDirectionHeuristicCompat = DEFAULT_TEXT_DIRECTION_HEURISTIC;
167c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio            mFlags = DEFAULT_FLAGS;
16877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
16977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
17077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
17177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Specifies whether the BidiFormatter to be built should also "reset" directionality before
172e3754c041d53f20faef1ee23f9b1b7e0c1c0f081Roozbeh Pournader         * a string being bidi-wrapped, not just after it. The default is true.
17377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
17477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        public Builder stereoReset(boolean stereoReset) {
17577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (stereoReset) {
176c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio                mFlags |= FLAG_STEREO_RESET;
17777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            } else {
178c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio                mFlags &= ~FLAG_STEREO_RESET;
17977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
18077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return this;
18177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
18277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
18377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
18477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Specifies the default directionality estimation algorithm to be used by the BidiFormatter.
18577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * By default, uses the first-strong heuristic.
18677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *
18777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @param heuristic the {@code TextDirectionHeuristic} to use.
18877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @return the builder itself.
18977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
19077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        public Builder setTextDirectionHeuristic(TextDirectionHeuristicCompat heuristic) {
191c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio            mTextDirectionHeuristicCompat = heuristic;
19277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return this;
19377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
19477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
19577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private static BidiFormatter getDefaultInstanceFromContext(boolean isRtlContext) {
19677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return isRtlContext ? DEFAULT_RTL_INSTANCE : DEFAULT_LTR_INSTANCE;
19777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
19877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
19977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
20077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @return A BidiFormatter with the specified options.
20177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
20277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        public BidiFormatter build() {
203c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio            if (mFlags == DEFAULT_FLAGS &&
204c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio                    mTextDirectionHeuristicCompat == DEFAULT_TEXT_DIRECTION_HEURISTIC) {
205c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio                return getDefaultInstanceFromContext(mIsRtlContext);
20677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
207c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio            return new BidiFormatter(mIsRtlContext, mFlags, mTextDirectionHeuristicCompat);
20877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
20977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
21077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
21177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    //
21277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final int FLAG_STEREO_RESET = 2;
21377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final int DEFAULT_FLAGS = FLAG_STEREO_RESET;
21477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
21577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final BidiFormatter DEFAULT_LTR_INSTANCE = new BidiFormatter(
21677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            false /* LTR context */,
21777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            DEFAULT_FLAGS,
21877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            DEFAULT_TEXT_DIRECTION_HEURISTIC);
21977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
22077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final BidiFormatter DEFAULT_RTL_INSTANCE = new BidiFormatter(
22177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            true /* RTL context */,
22277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            DEFAULT_FLAGS,
22377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            DEFAULT_TEXT_DIRECTION_HEURISTIC);
22477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
225c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio    private final boolean mIsRtlContext;
226c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio    private final int mFlags;
227c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio    private final TextDirectionHeuristicCompat mDefaultTextDirectionHeuristicCompat;
22877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
22977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
23057050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio     * Factory for creating an instance of BidiFormatter for the default locale directionality.
23157050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio     *
23257050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio     */
23357050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio    public static BidiFormatter getInstance() {
23457050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio        return new Builder().build();
23557050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio    }
23657050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio
23757050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio    /**
23877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Factory for creating an instance of BidiFormatter given the context directionality.
23977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
24077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param rtlContext Whether the context directionality is RTL.
24177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
24277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public static BidiFormatter getInstance(boolean rtlContext) {
24377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return new Builder(rtlContext).build();
24477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
24577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
24677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
24777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Factory for creating an instance of BidiFormatter given the context locale.
24877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
24977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param locale The context locale.
25077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
25177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public static BidiFormatter getInstance(Locale locale) {
25277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return new Builder(locale).build();
25377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
25477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
25577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
25677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param isRtlContext Whether the context directionality is RTL or not.
25777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param flags The option flags.
25877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The default text direction heuristic.
25977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
26077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private BidiFormatter(boolean isRtlContext, int flags, TextDirectionHeuristicCompat heuristic) {
261c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        mIsRtlContext = isRtlContext;
262c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        mFlags = flags;
263c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        mDefaultTextDirectionHeuristicCompat = heuristic;
26477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
26577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
26677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
26777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Whether the context directionality is RTL
26877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
26977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public boolean isRtlContext() {
270c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return mIsRtlContext;
27177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
27277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
27377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
27477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Whether directionality "reset" should also be done before a string being
27577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * bidi-wrapped, not just after it.
27677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
27777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public boolean getStereoReset() {
278c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return (mFlags & FLAG_STEREO_RESET) != 0;
27977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
28077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
28177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
28277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns a Unicode bidi mark matching the context directionality (LRM or RLM) if either the
28377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * overall or the exit directionality of a given string is opposite to the context directionality.
28477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Putting this after the string (including its directionality declaration wrapping) prevents it
28577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * from "sticking" to other opposite-directionality text or a number appearing after it inline
28677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * with only neutral content in between. Otherwise returns the empty string. While the exit
28777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * directionality is determined by scanning the end of the string, the overall directionality is
288b37703fef07bb1940fd9d933bb9cc5b6c3959cb2Fabrice Di Meglio     * given explicitly by a heuristic to estimate the {@code str}'s directionality.
28977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
29077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str String after which the mark may need to appear.
29177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s
29277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *                  directionality.
29377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context;
29477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     else, the empty string.
29577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
296b37703fef07bb1940fd9d933bb9cc5b6c3959cb2Fabrice Di Meglio    private String markAfter(String str, TextDirectionHeuristicCompat heuristic) {
29777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        final boolean isRtl = heuristic.isRtl(str, 0, str.length());
29877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        // getExitDir() is called only if needed (short-circuit).
299c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        if (!mIsRtlContext && (isRtl || getExitDir(str) == DIR_RTL)) {
30077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return LRM_STRING;
30177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
302c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        if (mIsRtlContext && (!isRtl || getExitDir(str) == DIR_LTR)) {
30377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return RLM_STRING;
30477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
30577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return EMPTY_STRING;
30677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
30777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
30877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
30977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns a Unicode bidi mark matching the context directionality (LRM or RLM) if either the
31077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * overall or the entry directionality of a given string is opposite to the context
31177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * directionality. Putting this before the string (including its directionality declaration
312b37703fef07bb1940fd9d933bb9cc5b6c3959cb2Fabrice Di Meglio     * wrapping) prevents it from "sticking" to other opposite-directionality text appearing before
313b37703fef07bb1940fd9d933bb9cc5b6c3959cb2Fabrice Di Meglio     * it inline with only neutral content in between. Otherwise returns the empty string. While the
31477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * entry directionality is determined by scanning the beginning of the string, the overall
315b37703fef07bb1940fd9d933bb9cc5b6c3959cb2Fabrice Di Meglio     * directionality is given explicitly by a heuristic to estimate the {@code str}'s directionality.
31677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
31777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str String before which the mark may need to appear.
31877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s
31977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *                  directionality.
32077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context;
32177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     else, the empty string.
32277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
323b37703fef07bb1940fd9d933bb9cc5b6c3959cb2Fabrice Di Meglio    private String markBefore(String str, TextDirectionHeuristicCompat heuristic) {
32477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        final boolean isRtl = heuristic.isRtl(str, 0, str.length());
32577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        // getEntryDir() is called only if needed (short-circuit).
326c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        if (!mIsRtlContext && (isRtl || getEntryDir(str) == DIR_RTL)) {
32777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return LRM_STRING;
32877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
329c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        if (mIsRtlContext && (!isRtl || getEntryDir(str) == DIR_LTR)) {
33077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return RLM_STRING;
33177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
33277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return EMPTY_STRING;
33377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
33477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
33577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
33677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Estimates the directionality of a string using the default text direction heuristic.
33777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
33877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str String whose directionality is to be estimated.
33977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return true if {@code str}'s estimated overall directionality is RTL. Otherwise returns
34077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *          false.
34177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
34277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public boolean isRtl(String str) {
343c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return mDefaultTextDirectionHeuristicCompat.isRtl(str, 0, str.length());
34477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
34577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
34677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
34777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Formats a string of given directionality for use in plain-text output of the context
34877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * directionality, so an opposite-directionality string is neither garbled nor garbles its
349b37703fef07bb1940fd9d933bb9cc5b6c3959cb2Fabrice Di Meglio     * surroundings. This makes use of Unicode bidi formatting characters.
35077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * <p>
35177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * The algorithm: In case the given directionality doesn't match the context directionality, wraps
35277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * the string with Unicode bidi formatting characters: RLE+{@code str}+PDF for RTL text, or
35377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * LRE+{@code str}+PDF for LTR text.
35477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * <p>
35577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * If {@code isolate}, directionally isolates the string so that it does not garble its
35677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * surroundings. Currently, this is done by "resetting" the directionality after the string by
35777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * appending a trailing Unicode bidi mark matching the context directionality (LRM or RLM) when
358e3754c041d53f20faef1ee23f9b1b7e0c1c0f081Roozbeh Pournader     * either the overall directionality or the exit directionality of the string is opposite to
359e3754c041d53f20faef1ee23f9b1b7e0c1c0f081Roozbeh Pournader     * that of the context. Unless the formatter was built using
360e3754c041d53f20faef1ee23f9b1b7e0c1c0f081Roozbeh Pournader     * {@link Builder#stereoReset(boolean)} with a {@code false} argument, also prepends a Unicode
361e3754c041d53f20faef1ee23f9b1b7e0c1c0f081Roozbeh Pournader     * bidi mark matching the context directionality when either the overall directionality or the
362e3754c041d53f20faef1ee23f9b1b7e0c1c0f081Roozbeh Pournader     * entry directionality of the string is opposite to that of the context. Note that as opposed
363e3754c041d53f20faef1ee23f9b1b7e0c1c0f081Roozbeh Pournader     * to the overall directionality, the entry and exit directionalities are determined from the
364e3754c041d53f20faef1ee23f9b1b7e0c1c0f081Roozbeh Pournader     * string itself.
36577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * <p>
36677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Does *not* do HTML-escaping.
36777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
36877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
36977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The algorithm to be used to estimate the string's overall direction.
37077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param isolate Whether to directionally isolate the string to prevent it from garbling the
37177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     content around it
372a404677a9abad23c78a35512cd7428f86724b30aRoozbeh Pournader     * @return Input string after applying the above processing. {@code null} if {@code str} is
373a404677a9abad23c78a35512cd7428f86724b30aRoozbeh Pournader     *     {@code null}.
37477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
37577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String unicodeWrap(String str, TextDirectionHeuristicCompat heuristic, boolean isolate) {
376a404677a9abad23c78a35512cd7428f86724b30aRoozbeh Pournader        if (str == null) return null;
37777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        final boolean isRtl = heuristic.isRtl(str, 0, str.length());
37877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        StringBuilder result = new StringBuilder();
37977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        if (getStereoReset() && isolate) {
38077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(markBefore(str,
38177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    isRtl ? TextDirectionHeuristicsCompat.RTL : TextDirectionHeuristicsCompat.LTR));
38277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
383c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        if (isRtl != mIsRtlContext) {
38477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(isRtl ? RLE : LRE);
38577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(str);
38677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(PDF);
38777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        } else {
38877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(str);
38977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
39077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        if (isolate) {
39177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(markAfter(str,
39277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    isRtl ? TextDirectionHeuristicsCompat.RTL : TextDirectionHeuristicsCompat.LTR));
39377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
39477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return result.toString();
39577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
39677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
39777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
39877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #unicodeWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but assumes
39977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * {@code isolate} is true.
40077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
40177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
40277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The algorithm to be used to estimate the string's overall direction.
40377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Input string after applying the above processing.
40477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
40577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String unicodeWrap(String str, TextDirectionHeuristicCompat heuristic) {
40677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return unicodeWrap(str, heuristic, true /* isolate */);
40777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
40877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
40977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
41077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #unicodeWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but uses the
41177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * formatter's default direction estimation algorithm.
41277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
41377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
41477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param isolate Whether to directionally isolate the string to prevent it from garbling the
41577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     content around it
41677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Input string after applying the above processing.
41777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
41877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String unicodeWrap(String str, boolean isolate) {
419c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return unicodeWrap(str, mDefaultTextDirectionHeuristicCompat, isolate);
42077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
42177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
42277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
42377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #unicodeWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but uses the
42477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * formatter's default direction estimation algorithm and assumes {@code isolate} is true.
42577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
42677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
42777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Input string after applying the above processing.
42877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
42977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String unicodeWrap(String str) {
430c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return unicodeWrap(str, mDefaultTextDirectionHeuristicCompat, true /* isolate */);
43177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
43277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
43377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
43477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Helper method to return true if the Locale directionality is RTL.
43577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
43677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param locale The Locale whose directionality will be checked to be RTL or LTR
43777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return true if the {@code locale} directionality is RTL. False otherwise.
43877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
43977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static boolean isRtlLocale(Locale locale) {
44077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return (TextUtilsCompat.getLayoutDirectionFromLocale(locale) == ViewCompat.LAYOUT_DIRECTION_RTL);
44177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
44277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
44377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
44477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Enum for directionality type.
44577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
44677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final int DIR_LTR = -1;
44777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final int DIR_UNKNOWN = 0;
44877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final int DIR_RTL = +1;
44977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
45077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
45177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns the directionality of the last character with strong directionality in the string, or
45277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * DIR_UNKNOWN if none was encountered. For efficiency, actually scans backwards from the end of
45377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * the string. Treats a non-BN character between an LRE/RLE/LRO/RLO and its matching PDF as a
45477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * strong character, LTR after LRE/LRO, and RTL after RLE/RLO. The results are undefined for a
45577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * string containing unbalanced LRE/RLE/LRO/RLO/PDF characters. The intended use is to check
45677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * whether a logically separate item that starts with a number or a character of the string's
45777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * exit directionality and follows this string inline (not counting any neutral characters in
45877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * between) would "stick" to it in an opposite-directionality context, thus being displayed in
45977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * an incorrect position. An LRM or RLM character (the one of the context's directionality)
46077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * between the two will prevent such sticking.
46177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
46277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str the string to check.
46377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
46477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static int getExitDir(String str) {
46577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return new DirectionalityEstimator(str, false /* isHtml */).getExitDir();
46677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
46777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
46877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
46977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns the directionality of the first character with strong directionality in the string,
47077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * or DIR_UNKNOWN if none was encountered. Treats a non-BN character between an
47177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * LRE/RLE/LRO/RLO and its matching PDF as a strong character, LTR after LRE/LRO, and RTL after
47277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * RLE/RLO. The results are undefined for a string containing unbalanced LRE/RLE/LRO/RLO/PDF
47377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * characters. The intended use is to check whether a logically separate item that ends with a
47477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * character of the string's entry directionality and precedes the string inline (not counting
47577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * any neutral characters in between) would "stick" to it in an opposite-directionality context,
47677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * thus being displayed in an incorrect position. An LRM or RLM character (the one of the
47777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * context's directionality) between the two will prevent such sticking.
47877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
47977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str the string to check.
48077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
48177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static int getEntryDir(String str) {
48277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return new DirectionalityEstimator(str, false /* isHtml */).getEntryDir();
48377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
48477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
48577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
48677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * An object that estimates the directionality of a given string by various methods.
48777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
48877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
48977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static class DirectionalityEstimator {
49077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
49177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        // Internal static variables and constants.
49277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
49377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
49477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Size of the bidi character class cache. The results of the Character.getDirectionality()
49577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * calls on the lowest DIR_TYPE_CACHE_SIZE codepoints are kept in an array for speed.
49677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The 0x700 value is designed to leave all the European and Near Eastern languages in the
49777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * cache. It can be reduced to 0x180, restricting the cache to the Western European
49877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * languages.
49977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
50077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private static final int DIR_TYPE_CACHE_SIZE = 0x700;
50177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
50277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
50377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The bidi character class cache.
50477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
50577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private static final byte DIR_TYPE_CACHE[];
50677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
50777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        static {
50877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            DIR_TYPE_CACHE = new byte[DIR_TYPE_CACHE_SIZE];
50977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            for (int i = 0; i < DIR_TYPE_CACHE_SIZE; i++) {
51077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                DIR_TYPE_CACHE[i] = Character.getDirectionality(i);
51177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
51277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
51377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
51477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        // Internal instance variables.
51577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
51677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
51777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The text to be scanned.
51877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
51977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private final String text;
52077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
52177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
52277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Whether the text to be scanned is to be treated as HTML, i.e. skipping over tags and
52377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * entities when looking for the next / preceding dir type.
52477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
52577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private final boolean isHtml;
52677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
52777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
52877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The length of the text in chars.
52977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
53077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private final int length;
53177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
53277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
53377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The current position in the text.
53477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
53577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private int charIndex;
53677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
53777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
53877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The char encountered by the last dirTypeForward or dirTypeBackward call. If it
53977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * encountered a supplementary codepoint, this contains a char that is not a valid
54077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * codepoint. This is ok, because this member is only used to detect some well-known ASCII
54177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * syntax, e.g. "http://" and the beginning of an HTML tag or entity.
54277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
54377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private char lastChar;
54477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
54577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
54677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Constructor.
54777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *
54877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @param text The string to scan.
54977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @param isHtml Whether the text to be scanned is to be treated as HTML, i.e. skipping over
55077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *     tags and entities.
55177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
55277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        DirectionalityEstimator(String text, boolean isHtml) {
55377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            this.text = text;
55477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            this.isHtml = isHtml;
55577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            length = text.length();
55677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
55777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
55877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
55977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Returns the directionality of the first character with strong directionality in the
56077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * string, or DIR_UNKNOWN if none was encountered. Treats a non-BN character between an
56177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * LRE/RLE/LRO/RLO and its matching PDF as a strong character, LTR after LRE/LRO, and RTL
56277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * after RLE/RLO. The results are undefined for a string containing unbalanced
56377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * LRE/RLE/LRO/RLO/PDF characters.
56477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
56577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        int getEntryDir() {
56677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // The reason for this method name, as opposed to getFirstStrongDir(), is that
56777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // "first strong" is a commonly used description of Unicode's estimation algorithm,
56877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // but the two must treat formatting characters quite differently. Thus, we are staying
56977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // away from both "first" and "last" in these method names to avoid confusion.
57077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex = 0;
57177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int embeddingLevel = 0;
57277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int embeddingLevelDir = DIR_UNKNOWN;
57377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int firstNonEmptyEmbeddingLevel = 0;
57477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex < length && firstNonEmptyEmbeddingLevel == 0) {
57577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                switch (dirTypeForward()) {
57677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
57777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
57877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        ++embeddingLevel;
57977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        embeddingLevelDir = DIR_LTR;
58077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
58177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
58277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
58377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        ++embeddingLevel;
58477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        embeddingLevelDir = DIR_RTL;
58577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
58677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT:
58777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        --embeddingLevel;
58877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        // To restore embeddingLevelDir to its previous value, we would need a
58977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        // stack, which we want to avoid. Thus, at this point we do not know the
59077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        // current embedding's directionality.
59177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        embeddingLevelDir = DIR_UNKNOWN;
59277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
59377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_BOUNDARY_NEUTRAL:
59477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
59577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
59677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (embeddingLevel == 0) {
59777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_LTR;
59877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
59977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        firstNonEmptyEmbeddingLevel = embeddingLevel;
60077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
60177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
60277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
60377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (embeddingLevel == 0) {
60477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_RTL;
60577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
60677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        firstNonEmptyEmbeddingLevel = embeddingLevel;
60777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
60877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    default:
60977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        firstNonEmptyEmbeddingLevel = embeddingLevel;
61077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
61177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
61277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
61377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
61477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // We have either found a non-empty embedding or scanned the entire string finding
61577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // neither a non-empty embedding nor a strong character outside of an embedding.
61677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (firstNonEmptyEmbeddingLevel == 0) {
61777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                // We have not found a non-empty embedding. Thus, the string contains neither a
61877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                // non-empty embedding nor a strong character outside of an embedding.
61977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                return DIR_UNKNOWN;
62077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
62177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
62277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // We have found a non-empty embedding.
62377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (embeddingLevelDir != DIR_UNKNOWN) {
62477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                // We know the directionality of the non-empty embedding.
62577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                return embeddingLevelDir;
62677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
62777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
62877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // We do not remember the directionality of the non-empty embedding we found. So, we go
62977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // backwards to find the start of the non-empty embedding and get its directionality.
63077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex > 0) {
63177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                switch (dirTypeBackward()) {
63277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
63377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
63477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (firstNonEmptyEmbeddingLevel == embeddingLevel) {
63577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_LTR;
63677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
63777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        --embeddingLevel;
63877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
63977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
64077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
64177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (firstNonEmptyEmbeddingLevel == embeddingLevel) {
64277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_RTL;
64377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
64477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        --embeddingLevel;
64577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
64677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT:
64777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        ++embeddingLevel;
64877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
64977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
65077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
65177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // We should never get here.
65277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return DIR_UNKNOWN;
65377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
65477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
65577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
65677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Returns the directionality of the last character with strong directionality in the
65777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * string, or DIR_UNKNOWN if none was encountered. For efficiency, actually scans backwards
65877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * from the end of the string. Treats a non-BN character between an LRE/RLE/LRO/RLO and its
65977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * matching PDF as a strong character, LTR after LRE/LRO, and RTL after RLE/RLO. The results
66077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * are undefined for a string containing unbalanced LRE/RLE/LRO/RLO/PDF characters.
66177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
66277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        int getExitDir() {
66377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // The reason for this method name, as opposed to getLastStrongDir(), is that "last
66477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // strong" sounds like the exact opposite of "first strong", which is a commonly used
66577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // description of Unicode's estimation algorithm (getUnicodeDir() above), but the two
66677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // must treat formatting characters quite differently. Thus, we are staying away from
66777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // both "first" and "last" in these method names to avoid confusion.
66877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex = length;
66977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int embeddingLevel = 0;
67077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int lastNonEmptyEmbeddingLevel = 0;
67177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex > 0) {
67277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                switch (dirTypeBackward()) {
67377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
67477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (embeddingLevel == 0) {
67577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_LTR;
67677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
67777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (lastNonEmptyEmbeddingLevel == 0) {
67877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            lastNonEmptyEmbeddingLevel = embeddingLevel;
67977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
68077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
68177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
68277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
68377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (lastNonEmptyEmbeddingLevel == embeddingLevel) {
68477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_LTR;
68577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
68677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        --embeddingLevel;
68777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
68877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
68977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
69077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (embeddingLevel == 0) {
69177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_RTL;
69277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
69377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (lastNonEmptyEmbeddingLevel == 0) {
69477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            lastNonEmptyEmbeddingLevel = embeddingLevel;
69577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
69677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
69777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
69877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
69977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (lastNonEmptyEmbeddingLevel == embeddingLevel) {
70077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_RTL;
70177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
70277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        --embeddingLevel;
70377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
70477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT:
70577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        ++embeddingLevel;
70677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
70777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_BOUNDARY_NEUTRAL:
70877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
70977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    default:
71077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (lastNonEmptyEmbeddingLevel == 0) {
71177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            lastNonEmptyEmbeddingLevel = embeddingLevel;
71277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
71377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
71477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
71577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
71677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return DIR_UNKNOWN;
71777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
71877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
71977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        // Internal methods
72077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
72177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
72277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Gets the bidi character class, i.e. Character.getDirectionality(), of a given char, using
72377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * a cache for speed. Not designed for supplementary codepoints, whose results we do not
72477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * cache.
72577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
72677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private static byte getCachedDirectionality(char c) {
72777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return c < DIR_TYPE_CACHE_SIZE ? DIR_TYPE_CACHE[c] : Character.getDirectionality(c);
72877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
72977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
73077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
73177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Returns the Character.DIRECTIONALITY_... value of the next codepoint and advances
73277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * charIndex. If isHtml, and the codepoint is '<' or '&', advances through the tag/entity,
73377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * and returns Character.DIRECTIONALITY_WHITESPACE. For an entity, it would be best to
73477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * figure out the actual character, and return its dirtype, but treating it as whitespace is
73577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * good enough for our purposes.
73677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *
73777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @throws java.lang.IndexOutOfBoundsException if called when charIndex >= length or < 0.
73877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
73977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        byte dirTypeForward() {
74077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            lastChar = text.charAt(charIndex);
74177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (Character.isHighSurrogate(lastChar)) {
74277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                int codePoint = Character.codePointAt(text, charIndex);
74377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                charIndex += Character.charCount(codePoint);
74477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                return Character.getDirectionality(codePoint);
74577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
74677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex++;
74777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            byte dirType = getCachedDirectionality(lastChar);
74877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (isHtml) {
74977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                // Process tags and entities.
75077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '<') {
75177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    dirType = skipTagForward();
75277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                } else if (lastChar == '&') {
75377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    dirType = skipEntityForward();
75477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
75577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
75677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return dirType;
75777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
75877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
75977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
76077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Returns the Character.DIRECTIONALITY_... value of the preceding codepoint and advances
76177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * charIndex backwards. If isHtml, and the codepoint is the end of a complete HTML tag or
76277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * entity, advances over the whole tag/entity and returns
76377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Character.DIRECTIONALITY_WHITESPACE. For an entity, it would be best to figure out the
76477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * actual character, and return its dirtype, but treating it as whitespace is good enough
76577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * for our purposes.
76677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *
76777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @throws java.lang.IndexOutOfBoundsException if called when charIndex > length or <= 0.
76877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
76977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        byte dirTypeBackward() {
77077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            lastChar = text.charAt(charIndex - 1);
77177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (Character.isLowSurrogate(lastChar)) {
77277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                int codePoint = Character.codePointBefore(text, charIndex);
77377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                charIndex -= Character.charCount(codePoint);
77477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                return Character.getDirectionality(codePoint);
77577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
77677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex--;
77777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            byte dirType = getCachedDirectionality(lastChar);
77877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (isHtml) {
77977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                // Process tags and entities.
78077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '>') {
78177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    dirType = skipTagBackward();
78277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                } else if (lastChar == ';') {
78377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    dirType = skipEntityBackward();
78477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
78577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
78677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return dirType;
78777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
78877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
78977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
79077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Advances charIndex forward through an HTML tag (after the opening &lt; has already been
79177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * read) and returns Character.DIRECTIONALITY_WHITESPACE. If there is no matching &gt;,
79277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * does not change charIndex and returns Character.DIRECTIONALITY_OTHER_NEUTRALS (for the
79377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * &lt; that hadn't been part of a tag after all).
79477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
79577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private byte skipTagForward() {
79677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int initialCharIndex = charIndex;
79777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex < length) {
79877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                lastChar = text.charAt(charIndex++);
79977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '>') {
80077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    // The end of the tag.
80177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    return Character.DIRECTIONALITY_WHITESPACE;
80277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
80377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '"' || lastChar == '\'') {
80477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    // Skip over a quoted attribute value inside the tag.
80577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    char quote = lastChar;
80677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    while (charIndex < length && (lastChar = text.charAt(charIndex++)) != quote) {}
80777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
80877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
80977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // The original '<' wasn't the start of a tag after all.
81077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex = initialCharIndex;
81177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            lastChar = '<';
81277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return Character.DIRECTIONALITY_OTHER_NEUTRALS;
81377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
81477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
81577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
81677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Advances charIndex backward through an HTML tag (after the closing &gt; has already been
81777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * read) and returns Character.DIRECTIONALITY_WHITESPACE. If there is no matching &lt;, does
81877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * not change charIndex and returns Character.DIRECTIONALITY_OTHER_NEUTRALS (for the &gt;
81977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * that hadn't been part of a tag after all). Nevertheless, the running time for calling
82077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * skipTagBackward() in a loop remains linear in the size of the text, even for a text like
82177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * "&gt;&gt;&gt;&gt;", because skipTagBackward() also stops looking for a matching &lt;
82277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * when it encounters another &gt;.
82377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
82477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private byte skipTagBackward() {
82577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int initialCharIndex = charIndex;
82677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex > 0) {
82777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                lastChar = text.charAt(--charIndex);
82877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '<') {
82977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    // The start of the tag.
83077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    return Character.DIRECTIONALITY_WHITESPACE;
83177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
83277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '>') {
83377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    break;
83477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
83577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '"' || lastChar == '\'') {
83677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    // Skip over a quoted attribute value inside the tag.
83777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    char quote = lastChar;
83877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    while (charIndex > 0 && (lastChar = text.charAt(--charIndex)) != quote) {}
83977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
84077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
84177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // The original '>' wasn't the end of a tag after all.
84277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex = initialCharIndex;
84377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            lastChar = '>';
84477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return Character.DIRECTIONALITY_OTHER_NEUTRALS;
84577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
84677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
84777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
84877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Advances charIndex forward through an HTML character entity tag (after the opening
84977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * &amp; has already been read) and returns Character.DIRECTIONALITY_WHITESPACE. It would be
85077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * best to figure out the actual character and return its dirtype, but this is good enough.
85177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
85277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private byte skipEntityForward() {
85377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex < length && (lastChar = text.charAt(charIndex++)) != ';') {}
85477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return Character.DIRECTIONALITY_WHITESPACE;
85577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
85677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
85777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
85877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Advances charIndex backward through an HTML character entity tag (after the closing ;
85977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * has already been read) and returns Character.DIRECTIONALITY_WHITESPACE. It would be best
86077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * to figure out the actual character and return its dirtype, but this is good enough.
86177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * If there is no matching &amp;, does not change charIndex and returns
86277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Character.DIRECTIONALITY_OTHER_NEUTRALS (for the ';' that did not start an entity after
86377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * all). Nevertheless, the running time for calling skipEntityBackward() in a loop remains
86477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * linear in the size of the text, even for a text like ";;;;;;;", because skipTagBackward()
86577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * also stops looking for a matching &amp; when it encounters another ;.
86677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
86777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private byte skipEntityBackward() {
86877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int initialCharIndex = charIndex;
86977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex > 0) {
87077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                lastChar = text.charAt(--charIndex);
87177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '&') {
87277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    return Character.DIRECTIONALITY_WHITESPACE;
87377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
87477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == ';') {
87577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    break;
87677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
87777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
87877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex = initialCharIndex;
87977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            lastChar = ';';
88077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return Character.DIRECTIONALITY_OTHER_NEUTRALS;
88177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
88277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
88377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio}