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 < has already been 78877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * read) and returns Character.DIRECTIONALITY_WHITESPACE. If there is no matching >, 78977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * does not change charIndex and returns Character.DIRECTIONALITY_OTHER_NEUTRALS (for the 79077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * < 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 > has already been 81477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * read) and returns Character.DIRECTIONALITY_WHITESPACE. If there is no matching <, does 81577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * not change charIndex and returns Character.DIRECTIONALITY_OTHER_NEUTRALS (for the > 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 * ">>>>", because skipTagBackward() also stops looking for a matching < 81977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * when it encounters another >. 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 * & 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 &, 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 & 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}