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
17277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * a string being bidi-wrapped, not just after it. The default is false.
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
35877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * either the overall directionality or the exit directionality of the string is opposite to that
35977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * of the context. If the formatter was built using {@link Builder#stereoReset(boolean)} and
36077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * passing "true" as an argument, also prepends a Unicode bidi mark matching the context
36177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * directionality when either the overall directionality or the entry directionality of the
36277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * string is opposite to that of the context. Note that as opposed to the overall
36377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * directionality, the entry and exit directionalities are determined from the string itself.
36477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * <p>
36577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Does *not* do HTML-escaping.
36677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
36777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
36877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The algorithm to be used to estimate the string's overall direction.
36977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param isolate Whether to directionally isolate the string to prevent it from garbling the
37077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     content around it
37177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Input string after applying the above processing.
37277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
37377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String unicodeWrap(String str, TextDirectionHeuristicCompat heuristic, boolean isolate) {
37477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        final boolean isRtl = heuristic.isRtl(str, 0, str.length());
37577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        StringBuilder result = new StringBuilder();
37677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        if (getStereoReset() && isolate) {
37777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(markBefore(str,
37877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    isRtl ? TextDirectionHeuristicsCompat.RTL : TextDirectionHeuristicsCompat.LTR));
37977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
380c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        if (isRtl != mIsRtlContext) {
38177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(isRtl ? RLE : LRE);
38277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(str);
38377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(PDF);
38477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        } else {
38577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(str);
38677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
38777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        if (isolate) {
38877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            result.append(markAfter(str,
38977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    isRtl ? TextDirectionHeuristicsCompat.RTL : TextDirectionHeuristicsCompat.LTR));
39077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
39177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return result.toString();
39277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
39377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
39477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
39577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #unicodeWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but assumes
39677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * {@code isolate} is true.
39777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
39877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
39977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param heuristic The algorithm to be used to estimate the string's overall direction.
40077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Input string after applying the above processing.
40177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
40277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String unicodeWrap(String str, TextDirectionHeuristicCompat heuristic) {
40377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return unicodeWrap(str, heuristic, true /* isolate */);
40477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
40577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
40677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
40777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #unicodeWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but uses the
40877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * formatter's default direction estimation algorithm.
40977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
41077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
41177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param isolate Whether to directionally isolate the string to prevent it from garbling the
41277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *     content around it
41377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Input string after applying the above processing.
41477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
41577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String unicodeWrap(String str, boolean isolate) {
416c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return unicodeWrap(str, mDefaultTextDirectionHeuristicCompat, isolate);
41777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
41877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
41977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
42077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Operates like {@link #unicodeWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but uses the
42177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * formatter's default direction estimation algorithm and assumes {@code isolate} is true.
42277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
42377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str The input string.
42477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return Input string after applying the above processing.
42577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
42677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    public String unicodeWrap(String str) {
427c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio        return unicodeWrap(str, mDefaultTextDirectionHeuristicCompat, true /* isolate */);
42877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
42977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
43077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
43177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Helper method to return true if the Locale directionality is RTL.
43277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
43377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param locale The Locale whose directionality will be checked to be RTL or LTR
43477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @return true if the {@code locale} directionality is RTL. False otherwise.
43577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
43677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static boolean isRtlLocale(Locale locale) {
43777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return (TextUtilsCompat.getLayoutDirectionFromLocale(locale) == ViewCompat.LAYOUT_DIRECTION_RTL);
43877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
43977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
44077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
44177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Enum for directionality type.
44277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
44377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final int DIR_LTR = -1;
44477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final int DIR_UNKNOWN = 0;
44577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static final int DIR_RTL = +1;
44677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
44777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
44877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns the directionality of the last character with strong directionality in the string, or
44977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * DIR_UNKNOWN if none was encountered. For efficiency, actually scans backwards from the end of
45077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * the string. Treats a non-BN character between an LRE/RLE/LRO/RLO and its matching PDF as a
45177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * strong character, LTR after LRE/LRO, and RTL after RLE/RLO. The results are undefined for a
45277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * string containing unbalanced LRE/RLE/LRO/RLO/PDF characters. The intended use is to check
45377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * whether a logically separate item that starts with a number or a character of the string's
45477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * exit directionality and follows this string inline (not counting any neutral characters in
45577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * between) would "stick" to it in an opposite-directionality context, thus being displayed in
45677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * an incorrect position. An LRM or RLM character (the one of the context's directionality)
45777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * between the two will prevent such sticking.
45877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
45977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str the string to check.
46077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
46177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static int getExitDir(String str) {
46277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return new DirectionalityEstimator(str, false /* isHtml */).getExitDir();
46377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
46477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
46577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
46677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * Returns the directionality of the first character with strong directionality in the string,
46777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * or DIR_UNKNOWN if none was encountered. Treats a non-BN character between an
46877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * LRE/RLE/LRO/RLO and its matching PDF as a strong character, LTR after LRE/LRO, and RTL after
46977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * RLE/RLO. The results are undefined for a string containing unbalanced LRE/RLE/LRO/RLO/PDF
47077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * characters. The intended use is to check whether a logically separate item that ends with a
47177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * character of the string's entry directionality and precedes the string inline (not counting
47277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * any neutral characters in between) would "stick" to it in an opposite-directionality context,
47377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * thus being displayed in an incorrect position. An LRM or RLM character (the one of the
47477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * context's directionality) between the two will prevent such sticking.
47577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
47677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * @param str the string to check.
47777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
47877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static int getEntryDir(String str) {
47977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        return new DirectionalityEstimator(str, false /* isHtml */).getEntryDir();
48077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
48177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
48277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    /**
48377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     * An object that estimates the directionality of a given string by various methods.
48477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     *
48577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio     */
48677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    private static class DirectionalityEstimator {
48777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
48877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        // Internal static variables and constants.
48977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
49077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
49177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Size of the bidi character class cache. The results of the Character.getDirectionality()
49277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * calls on the lowest DIR_TYPE_CACHE_SIZE codepoints are kept in an array for speed.
49377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The 0x700 value is designed to leave all the European and Near Eastern languages in the
49477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * cache. It can be reduced to 0x180, restricting the cache to the Western European
49577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * languages.
49677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
49777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private static final int DIR_TYPE_CACHE_SIZE = 0x700;
49877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
49977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
50077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The bidi character class cache.
50177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
50277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private static final byte DIR_TYPE_CACHE[];
50377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
50477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        static {
50577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            DIR_TYPE_CACHE = new byte[DIR_TYPE_CACHE_SIZE];
50677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            for (int i = 0; i < DIR_TYPE_CACHE_SIZE; i++) {
50777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                DIR_TYPE_CACHE[i] = Character.getDirectionality(i);
50877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
50977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
51077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
51177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        // Internal instance variables.
51277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
51377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
51477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The text to be scanned.
51577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
51677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private final String text;
51777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
51877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
51977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Whether the text to be scanned is to be treated as HTML, i.e. skipping over tags and
52077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * entities when looking for the next / preceding dir type.
52177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
52277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private final boolean isHtml;
52377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
52477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
52577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The length of the text in chars.
52677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
52777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private final int length;
52877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
52977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
53077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The current position in the text.
53177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
53277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private int charIndex;
53377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
53477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
53577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * The char encountered by the last dirTypeForward or dirTypeBackward call. If it
53677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * encountered a supplementary codepoint, this contains a char that is not a valid
53777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * codepoint. This is ok, because this member is only used to detect some well-known ASCII
53877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * syntax, e.g. "http://" and the beginning of an HTML tag or entity.
53977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
54077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private char lastChar;
54177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
54277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
54377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Constructor.
54477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *
54577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @param text The string to scan.
54677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @param isHtml Whether the text to be scanned is to be treated as HTML, i.e. skipping over
54777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *     tags and entities.
54877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
54977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        DirectionalityEstimator(String text, boolean isHtml) {
55077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            this.text = text;
55177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            this.isHtml = isHtml;
55277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            length = text.length();
55377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
55477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
55577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
55677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Returns the directionality of the first character with strong directionality in the
55777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * string, or DIR_UNKNOWN if none was encountered. Treats a non-BN character between an
55877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * LRE/RLE/LRO/RLO and its matching PDF as a strong character, LTR after LRE/LRO, and RTL
55977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * after RLE/RLO. The results are undefined for a string containing unbalanced
56077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * LRE/RLE/LRO/RLO/PDF characters.
56177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
56277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        int getEntryDir() {
56377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // The reason for this method name, as opposed to getFirstStrongDir(), is that
56477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // "first strong" is a commonly used description of Unicode's estimation algorithm,
56577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // but the two must treat formatting characters quite differently. Thus, we are staying
56677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // away from both "first" and "last" in these method names to avoid confusion.
56777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex = 0;
56877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int embeddingLevel = 0;
56977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int embeddingLevelDir = DIR_UNKNOWN;
57077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int firstNonEmptyEmbeddingLevel = 0;
57177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex < length && firstNonEmptyEmbeddingLevel == 0) {
57277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                switch (dirTypeForward()) {
57377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
57477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
57577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        ++embeddingLevel;
57677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        embeddingLevelDir = DIR_LTR;
57777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
57877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
57977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
58077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        ++embeddingLevel;
58177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        embeddingLevelDir = DIR_RTL;
58277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
58377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT:
58477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        --embeddingLevel;
58577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        // To restore embeddingLevelDir to its previous value, we would need a
58677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        // stack, which we want to avoid. Thus, at this point we do not know the
58777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        // current embedding's directionality.
58877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        embeddingLevelDir = DIR_UNKNOWN;
58977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
59077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_BOUNDARY_NEUTRAL:
59177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
59277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
59377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (embeddingLevel == 0) {
59477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_LTR;
59577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
59677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        firstNonEmptyEmbeddingLevel = embeddingLevel;
59777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
59877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
59977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
60077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (embeddingLevel == 0) {
60177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_RTL;
60277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
60377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        firstNonEmptyEmbeddingLevel = embeddingLevel;
60477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
60577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    default:
60677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        firstNonEmptyEmbeddingLevel = embeddingLevel;
60777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
60877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
60977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
61077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
61177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // We have either found a non-empty embedding or scanned the entire string finding
61277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // neither a non-empty embedding nor a strong character outside of an embedding.
61377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (firstNonEmptyEmbeddingLevel == 0) {
61477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                // We have not found a non-empty embedding. Thus, the string contains neither a
61577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                // non-empty embedding nor a strong character outside of an embedding.
61677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                return DIR_UNKNOWN;
61777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
61877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
61977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // We have found a non-empty embedding.
62077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (embeddingLevelDir != DIR_UNKNOWN) {
62177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                // We know the directionality of the non-empty embedding.
62277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                return embeddingLevelDir;
62377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
62477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
62577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // We do not remember the directionality of the non-empty embedding we found. So, we go
62677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // backwards to find the start of the non-empty embedding and get its directionality.
62777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex > 0) {
62877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                switch (dirTypeBackward()) {
62977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
63077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
63177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (firstNonEmptyEmbeddingLevel == embeddingLevel) {
63277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_LTR;
63377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
63477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        --embeddingLevel;
63577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
63677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
63777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
63877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (firstNonEmptyEmbeddingLevel == embeddingLevel) {
63977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_RTL;
64077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
64177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        --embeddingLevel;
64277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
64377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT:
64477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        ++embeddingLevel;
64577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
64677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
64777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
64877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // We should never get here.
64977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return DIR_UNKNOWN;
65077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
65177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
65277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
65377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Returns the directionality of the last character with strong directionality in the
65477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * string, or DIR_UNKNOWN if none was encountered. For efficiency, actually scans backwards
65577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * from the end of the string. Treats a non-BN character between an LRE/RLE/LRO/RLO and its
65677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * matching PDF as a strong character, LTR after LRE/LRO, and RTL after RLE/RLO. The results
65777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * are undefined for a string containing unbalanced LRE/RLE/LRO/RLO/PDF characters.
65877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
65977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        int getExitDir() {
66077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // The reason for this method name, as opposed to getLastStrongDir(), is that "last
66177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // strong" sounds like the exact opposite of "first strong", which is a commonly used
66277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // description of Unicode's estimation algorithm (getUnicodeDir() above), but the two
66377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // must treat formatting characters quite differently. Thus, we are staying away from
66477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // both "first" and "last" in these method names to avoid confusion.
66577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex = length;
66677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int embeddingLevel = 0;
66777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int lastNonEmptyEmbeddingLevel = 0;
66877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex > 0) {
66977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                switch (dirTypeBackward()) {
67077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
67177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (embeddingLevel == 0) {
67277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_LTR;
67377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
67477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (lastNonEmptyEmbeddingLevel == 0) {
67577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            lastNonEmptyEmbeddingLevel = embeddingLevel;
67677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
67777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
67877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
67977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
68077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (lastNonEmptyEmbeddingLevel == embeddingLevel) {
68177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_LTR;
68277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
68377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        --embeddingLevel;
68477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
68577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
68677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
68777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (embeddingLevel == 0) {
68877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_RTL;
68977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
69077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (lastNonEmptyEmbeddingLevel == 0) {
69177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            lastNonEmptyEmbeddingLevel = embeddingLevel;
69277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
69377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
69477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
69577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
69677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (lastNonEmptyEmbeddingLevel == embeddingLevel) {
69777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            return DIR_RTL;
69877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
69977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        --embeddingLevel;
70077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
70177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT:
70277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        ++embeddingLevel;
70377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
70477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    case Character.DIRECTIONALITY_BOUNDARY_NEUTRAL:
70577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
70677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    default:
70777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        if (lastNonEmptyEmbeddingLevel == 0) {
70877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                            lastNonEmptyEmbeddingLevel = embeddingLevel;
70977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        }
71077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                        break;
71177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
71277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
71377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return DIR_UNKNOWN;
71477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
71577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
71677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        // Internal methods
71777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
71877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
71977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Gets the bidi character class, i.e. Character.getDirectionality(), of a given char, using
72077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * a cache for speed. Not designed for supplementary codepoints, whose results we do not
72177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * cache.
72277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
72377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private static byte getCachedDirectionality(char c) {
72477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return c < DIR_TYPE_CACHE_SIZE ? DIR_TYPE_CACHE[c] : Character.getDirectionality(c);
72577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
72677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
72777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
72877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Returns the Character.DIRECTIONALITY_... value of the next codepoint and advances
72977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * charIndex. If isHtml, and the codepoint is '<' or '&', advances through the tag/entity,
73077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * and returns Character.DIRECTIONALITY_WHITESPACE. For an entity, it would be best to
73177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * figure out the actual character, and return its dirtype, but treating it as whitespace is
73277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * good enough for our purposes.
73377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *
73477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @throws java.lang.IndexOutOfBoundsException if called when charIndex >= length or < 0.
73577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
73677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        byte dirTypeForward() {
73777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            lastChar = text.charAt(charIndex);
73877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (Character.isHighSurrogate(lastChar)) {
73977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                int codePoint = Character.codePointAt(text, charIndex);
74077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                charIndex += Character.charCount(codePoint);
74177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                return Character.getDirectionality(codePoint);
74277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
74377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex++;
74477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            byte dirType = getCachedDirectionality(lastChar);
74577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (isHtml) {
74677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                // Process tags and entities.
74777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '<') {
74877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    dirType = skipTagForward();
74977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                } else if (lastChar == '&') {
75077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    dirType = skipEntityForward();
75177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
75277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
75377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return dirType;
75477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
75577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
75677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
75777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Returns the Character.DIRECTIONALITY_... value of the preceding codepoint and advances
75877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * charIndex backwards. If isHtml, and the codepoint is the end of a complete HTML tag or
75977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * entity, advances over the whole tag/entity and returns
76077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Character.DIRECTIONALITY_WHITESPACE. For an entity, it would be best to figure out the
76177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * actual character, and return its dirtype, but treating it as whitespace is good enough
76277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * for our purposes.
76377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         *
76477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * @throws java.lang.IndexOutOfBoundsException if called when charIndex > length or <= 0.
76577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
76677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        byte dirTypeBackward() {
76777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            lastChar = text.charAt(charIndex - 1);
76877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (Character.isLowSurrogate(lastChar)) {
76977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                int codePoint = Character.codePointBefore(text, charIndex);
77077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                charIndex -= Character.charCount(codePoint);
77177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                return Character.getDirectionality(codePoint);
77277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
77377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex--;
77477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            byte dirType = getCachedDirectionality(lastChar);
77577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            if (isHtml) {
77677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                // Process tags and entities.
77777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '>') {
77877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    dirType = skipTagBackward();
77977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                } else if (lastChar == ';') {
78077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    dirType = skipEntityBackward();
78177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
78277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
78377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return dirType;
78477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
78577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
78677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
78777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Advances charIndex forward through an HTML tag (after the opening &lt; has already been
78877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * read) and returns Character.DIRECTIONALITY_WHITESPACE. If there is no matching &gt;,
78977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * does not change charIndex and returns Character.DIRECTIONALITY_OTHER_NEUTRALS (for the
79077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * &lt; that hadn't been part of a tag after all).
79177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
79277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private byte skipTagForward() {
79377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int initialCharIndex = charIndex;
79477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex < length) {
79577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                lastChar = text.charAt(charIndex++);
79677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '>') {
79777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    // The end of the tag.
79877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    return Character.DIRECTIONALITY_WHITESPACE;
79977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
80077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '"' || lastChar == '\'') {
80177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    // Skip over a quoted attribute value inside the tag.
80277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    char quote = lastChar;
80377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    while (charIndex < length && (lastChar = text.charAt(charIndex++)) != quote) {}
80477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
80577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
80677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // The original '<' wasn't the start of a tag after all.
80777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex = initialCharIndex;
80877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            lastChar = '<';
80977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return Character.DIRECTIONALITY_OTHER_NEUTRALS;
81077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
81177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
81277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
81377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Advances charIndex backward through an HTML tag (after the closing &gt; has already been
81477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * read) and returns Character.DIRECTIONALITY_WHITESPACE. If there is no matching &lt;, does
81577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * not change charIndex and returns Character.DIRECTIONALITY_OTHER_NEUTRALS (for the &gt;
81677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * that hadn't been part of a tag after all). Nevertheless, the running time for calling
81777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * skipTagBackward() in a loop remains linear in the size of the text, even for a text like
81877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * "&gt;&gt;&gt;&gt;", because skipTagBackward() also stops looking for a matching &lt;
81977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * when it encounters another &gt;.
82077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
82177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private byte skipTagBackward() {
82277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int initialCharIndex = charIndex;
82377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex > 0) {
82477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                lastChar = text.charAt(--charIndex);
82577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '<') {
82677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    // The start of the tag.
82777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    return Character.DIRECTIONALITY_WHITESPACE;
82877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
82977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '>') {
83077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    break;
83177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
83277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '"' || lastChar == '\'') {
83377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    // Skip over a quoted attribute value inside the tag.
83477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    char quote = lastChar;
83577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    while (charIndex > 0 && (lastChar = text.charAt(--charIndex)) != quote) {}
83677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
83777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
83877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            // The original '>' wasn't the end of a tag after all.
83977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex = initialCharIndex;
84077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            lastChar = '>';
84177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return Character.DIRECTIONALITY_OTHER_NEUTRALS;
84277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
84377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
84477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
84577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Advances charIndex forward through an HTML character entity tag (after the opening
84677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * &amp; has already been read) and returns Character.DIRECTIONALITY_WHITESPACE. It would be
84777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * best to figure out the actual character and return its dirtype, but this is good enough.
84877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
84977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private byte skipEntityForward() {
85077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex < length && (lastChar = text.charAt(charIndex++)) != ';') {}
85177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return Character.DIRECTIONALITY_WHITESPACE;
85277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
85377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio
85477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        /**
85577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Advances charIndex backward through an HTML character entity tag (after the closing ;
85677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * has already been read) and returns Character.DIRECTIONALITY_WHITESPACE. It would be best
85777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * to figure out the actual character and return its dirtype, but this is good enough.
85877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * If there is no matching &amp;, does not change charIndex and returns
85977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * Character.DIRECTIONALITY_OTHER_NEUTRALS (for the ';' that did not start an entity after
86077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * all). Nevertheless, the running time for calling skipEntityBackward() in a loop remains
86177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * linear in the size of the text, even for a text like ";;;;;;;", because skipTagBackward()
86277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         * also stops looking for a matching &amp; when it encounters another ;.
86377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio         */
86477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        private byte skipEntityBackward() {
86577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            int initialCharIndex = charIndex;
86677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            while (charIndex > 0) {
86777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                lastChar = text.charAt(--charIndex);
86877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == '&') {
86977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    return Character.DIRECTIONALITY_WHITESPACE;
87077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
87177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                if (lastChar == ';') {
87277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                    break;
87377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio                }
87477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            }
87577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            charIndex = initialCharIndex;
87677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            lastChar = ';';
87777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio            return Character.DIRECTIONALITY_OTHER_NEUTRALS;
87877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio        }
87977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio    }
88077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio}