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 17ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikaspackage androidx.core.text; 1877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 199dede51868bbbe16aadcd65e04860bea8ea50e05Aurimas Liutikasimport static androidx.core.text.TextDirectionHeuristicsCompat.FIRSTSTRONG_LTR; 209dede51868bbbe16aadcd65e04860bea8ea50e05Aurimas Liutikas 21f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournaderimport android.text.SpannableStringBuilder; 2277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 239dede51868bbbe16aadcd65e04860bea8ea50e05Aurimas Liutikasimport androidx.core.view.ViewCompat; 2477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 259dede51868bbbe16aadcd65e04860bea8ea50e05Aurimas Liutikasimport java.util.Locale; 2677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 2777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio/** 2877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Utility class for formatting text for display in a potentially opposite-directionality context 2977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * without garbling. The directionality of the context is set at formatter creation and the 3077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality of the text can be either estimated or passed in when known. Provides the 3177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * following functionality: 3277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p> 3377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 1. Bidi Wrapping 3477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * When text in one language is mixed into a document in another, opposite-directionality language, 3577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * e.g. when an English business name is embedded in a Hebrew web page, both the inserted string 3677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * and the text surrounding it may be displayed incorrectly unless the inserted string is explicitly 3777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * separated from the surrounding text in a "wrapper" that: 3877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p> 39b37703fef07bb1940fd9d933bb9cc5b6c3959cb2Fabrice Di Meglio * - Declares its directionality so that the string is displayed correctly. This can be done in 40b37703fef07bb1940fd9d933bb9cc5b6c3959cb2Fabrice Di Meglio * Unicode bidi formatting codes by {@link #unicodeWrap} and similar methods. 4177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p> 4277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * - Isolates the string's directionality, so it does not unduly affect the surrounding content. 4377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Currently, this can only be done using invisible Unicode characters of the same direction as 4477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * the context (LRM or RLM) in addition to the directionality declaration above, thus "resetting" 4577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * the directionality to that of the context. The "reset" may need to be done at both ends of the 4677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * string. Without "reset" after the string, the string will "stick" to a number or logically 4777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * separate opposite-direction text that happens to follow it in-line (even if separated by 4877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * neutral content like spaces and punctuation). Without "reset" before the string, the same can 4977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * happen there, but only with more opposite-direction text, not a number. One approach is to 5077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * "reset" the direction only after each string, on the theory that if the preceding opposite- 5177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * direction text is itself bidi-wrapped, the "reset" after it will prevent the sticking. (Doing 5277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * the "reset" only before each string definitely does not work because we do not want to require 5377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * bidi-wrapping numbers, and a bidi-wrapped opposite-direction string could be followed by a 5477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * number.) Still, the safest policy is to do the "reset" on both ends of each string, since RTL 5577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * message translations often contain untranslated Latin-script brand names and technical terms, 5677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * and one of these can be followed by a bidi-wrapped inserted value. On the other hand, when one 5777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * has such a message, it is best to do the "reset" manually in the message translation itself, 5877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * since the message's opposite-direction text could be followed by an inserted number, which we 5977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * would not bidi-wrap anyway. Thus, "reset" only after the string is the current default. In an 6077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * alternative to "reset", recent additions to the HTML, CSS, and Unicode standards allow the 6177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * isolation to be part of the directionality declaration. This form of isolation is better than 6277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * "reset" because it takes less space, does not require knowing the context directionality, has a 6377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * gentler effect than "reset", and protects both ends of the string. However, we do not yet allow 6477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * using it because required platforms do not yet support it. 6577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p> 6677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Providing these wrapping services is the basic purpose of the bidi formatter. 6777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p> 6877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 2. Directionality estimation 6977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * How does one know whether a string about to be inserted into surrounding text has the same 7077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality? Well, in many cases, one knows that this must be the case when writing the code 7177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * doing the insertion, e.g. when a localized message is inserted into a localized page. In such 7277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * cases there is no need to involve the bidi formatter at all. In some other cases, it need not be 7377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * the same as the context, but is either constant (e.g. urls are always LTR) or otherwise known. 7477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * In the remaining cases, e.g. when the string is user-entered or comes from a database, the 7577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * language of the string (and thus its directionality) is not known a priori, and must be 7677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * estimated at run-time. The bidi formatter can do this automatically using the default 7777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * first-strong estimation algorithm. It can also be configured to use a custom directionality 7877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * estimation object. 7977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 8077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Megliopublic final class BidiFormatter { 8177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 8277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 8377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * The default text direction heuristic. 8477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 858eeb6c6cdd4bdcb6807cd8152e1e36b4883a8fa5Jake Wharton static final TextDirectionHeuristicCompat DEFAULT_TEXT_DIRECTION_HEURISTIC = FIRSTSTRONG_LTR; 8677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 8777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 8877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Unicode "Left-To-Right Embedding" (LRE) character. 8977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 9077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final char LRE = '\u202A'; 9177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 9277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 9377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Unicode "Right-To-Left Embedding" (RLE) character. 9477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 9577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final char RLE = '\u202B'; 9677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 9777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 9877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Unicode "Pop Directional Formatting" (PDF) character. 9977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 10077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final char PDF = '\u202C'; 10177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 10277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 10377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Unicode "Left-To-Right Mark" (LRM) character. 10477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 10577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final char LRM = '\u200E'; 10677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 10777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /* 10877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Unicode "Right-To-Left Mark" (RLM) character. 10977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 11077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final char RLM = '\u200F'; 11177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 11277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /* 11377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * String representation of LRM 11477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 11577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final String LRM_STRING = Character.toString(LRM); 11677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 11777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /* 11877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * String representation of RLM 11977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 12077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final String RLM_STRING = Character.toString(RLM); 12177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 12277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 12377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Empty string constant. 12477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 12577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final String EMPTY_STRING = ""; 12677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 12777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 12877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * A class for building a BidiFormatter with non-default options. 12977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 13077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public static final class Builder { 131c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio private boolean mIsRtlContext; 132c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio private int mFlags; 133c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio private TextDirectionHeuristicCompat mTextDirectionHeuristicCompat; 13477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 13577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 13677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Constructor. 13777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 13877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 13977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public Builder() { 14077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio initialize(isRtlLocale(Locale.getDefault())); 14177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 14277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 14377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 14477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Constructor. 14577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 14677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param rtlContext Whether the context directionality is RTL. 14777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 14877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public Builder(boolean rtlContext) { 14977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio initialize(rtlContext); 15077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 15177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 15277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 15377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Constructor. 15477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 15577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param locale The context locale. 15677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 15777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public Builder(Locale locale) { 15877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio initialize(isRtlLocale(locale)); 15977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 16077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 16177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 16277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Initializes the builder with the given context directionality and default options. 16377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 16477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param isRtlContext Whether the context is RTL or not. 16577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 16677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private void initialize(boolean isRtlContext) { 167c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio mIsRtlContext = isRtlContext; 168c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio mTextDirectionHeuristicCompat = DEFAULT_TEXT_DIRECTION_HEURISTIC; 169c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio mFlags = DEFAULT_FLAGS; 17077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 17177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 17277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 17377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Specifies whether the BidiFormatter to be built should also "reset" directionality before 174e3754c041d53f20faef1ee23f9b1b7e0c1c0f081Roozbeh Pournader * a string being bidi-wrapped, not just after it. The default is true. 17577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 17677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public Builder stereoReset(boolean stereoReset) { 17777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (stereoReset) { 178c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio mFlags |= FLAG_STEREO_RESET; 17977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } else { 180c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio mFlags &= ~FLAG_STEREO_RESET; 18177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 18277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return this; 18377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 18477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 18577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 18677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Specifies the default directionality estimation algorithm to be used by the BidiFormatter. 18777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * By default, uses the first-strong heuristic. 18877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 18977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param heuristic the {@code TextDirectionHeuristic} to use. 19077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return the builder itself. 19177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 19277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public Builder setTextDirectionHeuristic(TextDirectionHeuristicCompat heuristic) { 193c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio mTextDirectionHeuristicCompat = heuristic; 19477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return this; 19577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 19677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 19777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static BidiFormatter getDefaultInstanceFromContext(boolean isRtlContext) { 19877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return isRtlContext ? DEFAULT_RTL_INSTANCE : DEFAULT_LTR_INSTANCE; 19977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 20077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 20177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 20277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return A BidiFormatter with the specified options. 20377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 20477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public BidiFormatter build() { 205c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio if (mFlags == DEFAULT_FLAGS && 206c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio mTextDirectionHeuristicCompat == DEFAULT_TEXT_DIRECTION_HEURISTIC) { 207c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio return getDefaultInstanceFromContext(mIsRtlContext); 20877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 209c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio return new BidiFormatter(mIsRtlContext, mFlags, mTextDirectionHeuristicCompat); 21077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 21177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 21277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 21377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // 21477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final int FLAG_STEREO_RESET = 2; 21577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final int DEFAULT_FLAGS = FLAG_STEREO_RESET; 21677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 2178eeb6c6cdd4bdcb6807cd8152e1e36b4883a8fa5Jake Wharton static final BidiFormatter DEFAULT_LTR_INSTANCE = new BidiFormatter( 21877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio false /* LTR context */, 21977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio DEFAULT_FLAGS, 22077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio DEFAULT_TEXT_DIRECTION_HEURISTIC); 22177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 2228eeb6c6cdd4bdcb6807cd8152e1e36b4883a8fa5Jake Wharton static final BidiFormatter DEFAULT_RTL_INSTANCE = new BidiFormatter( 22377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio true /* RTL context */, 22477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio DEFAULT_FLAGS, 22577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio DEFAULT_TEXT_DIRECTION_HEURISTIC); 22677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 227c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio private final boolean mIsRtlContext; 228c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio private final int mFlags; 229c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio private final TextDirectionHeuristicCompat mDefaultTextDirectionHeuristicCompat; 23077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 23177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 23257050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio * Factory for creating an instance of BidiFormatter for the default locale directionality. 23357050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio * 23457050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio */ 23557050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio public static BidiFormatter getInstance() { 23657050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio return new Builder().build(); 23757050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio } 23857050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio 23957050da5a4c11fb1dd740599671540cc53b29cf7Fabrice Di Meglio /** 24077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Factory for creating an instance of BidiFormatter given the context directionality. 24177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 24277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param rtlContext Whether the context directionality is RTL. 24377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 24477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public static BidiFormatter getInstance(boolean rtlContext) { 24577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return new Builder(rtlContext).build(); 24677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 24777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 24877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 24977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Factory for creating an instance of BidiFormatter given the context locale. 25077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 25177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param locale The context locale. 25277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 25377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public static BidiFormatter getInstance(Locale locale) { 25477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return new Builder(locale).build(); 25577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 25677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 25777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 25877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param isRtlContext Whether the context directionality is RTL or not. 25977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param flags The option flags. 26077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param heuristic The default text direction heuristic. 26177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 2628eeb6c6cdd4bdcb6807cd8152e1e36b4883a8fa5Jake Wharton BidiFormatter(boolean isRtlContext, int flags, TextDirectionHeuristicCompat heuristic) { 263c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio mIsRtlContext = isRtlContext; 264c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio mFlags = flags; 265c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio mDefaultTextDirectionHeuristicCompat = heuristic; 26677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 26777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 26877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 26977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return Whether the context directionality is RTL 27077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 27177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public boolean isRtlContext() { 272c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio return mIsRtlContext; 27377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 27477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 27577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 27677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return Whether directionality "reset" should also be done before a string being 27777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * bidi-wrapped, not just after it. 27877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 27977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public boolean getStereoReset() { 280c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio return (mFlags & FLAG_STEREO_RESET) != 0; 28177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 28277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 28377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 28477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Returns a Unicode bidi mark matching the context directionality (LRM or RLM) if either the 285f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * overall or the exit directionality of a given CharSequence is opposite to the context 286f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * directionality. Putting this after the CharSequence (including its directionality 287f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * declaration wrapping) prevents it from "sticking" to other opposite-directionality text or a 288f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * number appearing after it inline with only neutral content in between. Otherwise returns 289f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * the empty string. While the exit directionality is determined by scanning the end of the 290f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * CharSequence, the overall directionality is given explicitly by a heuristic to estimate the 291f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * {@code str}'s directionality. 29277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 293f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * @param str CharSequence after which the mark may need to appear. 29477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s 29577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality. 29677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context; 297f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * else, the empty . 29877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 299f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader private String markAfter(CharSequence str, TextDirectionHeuristicCompat heuristic) { 30077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio final boolean isRtl = heuristic.isRtl(str, 0, str.length()); 30177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // getExitDir() is called only if needed (short-circuit). 302c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio if (!mIsRtlContext && (isRtl || getExitDir(str) == DIR_RTL)) { 30377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return LRM_STRING; 30477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 305c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio if (mIsRtlContext && (!isRtl || getExitDir(str) == DIR_LTR)) { 30677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return RLM_STRING; 30777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 30877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return EMPTY_STRING; 30977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 31077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 31177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 31277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Returns a Unicode bidi mark matching the context directionality (LRM or RLM) if either the 313f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * overall or the entry directionality of a given CharSequence is opposite to the context 314f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * directionality. Putting this before the CharSequence (including its directionality 315f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * declaration wrapping) prevents it from "sticking" to other opposite-directionality text 316f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * appearing before it inline with only neutral content in between. Otherwise returns the 317f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * empty string. While the entry directionality is determined by scanning the beginning of the 318f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * CharSequence, the overall directionality is given explicitly by a heuristic to estimate the 319f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * {@code str}'s directionality. 32077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 321f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * @param str CharSequence before which the mark may need to appear. 32277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s 32377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality. 32477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context; 32577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * else, the empty string. 32677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 327f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader private String markBefore(CharSequence str, TextDirectionHeuristicCompat heuristic) { 32877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio final boolean isRtl = heuristic.isRtl(str, 0, str.length()); 32977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // getEntryDir() is called only if needed (short-circuit). 330c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio if (!mIsRtlContext && (isRtl || getEntryDir(str) == DIR_RTL)) { 33177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return LRM_STRING; 33277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 333c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio if (mIsRtlContext && (!isRtl || getEntryDir(str) == DIR_LTR)) { 33477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return RLM_STRING; 33577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 33677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return EMPTY_STRING; 33777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 33877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 33977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 34077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Estimates the directionality of a string using the default text direction heuristic. 34177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 34277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str String whose directionality is to be estimated. 34377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return true if {@code str}'s estimated overall directionality is RTL. Otherwise returns 34477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * false. 34577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 34677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public boolean isRtl(String str) { 347f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader return isRtl((CharSequence) str); 348f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader } 349f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader 350f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader /** 351f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * Operates like {@link #isRtl(String)}, but takes a CharSequence instead of a string. 352f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * 353f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * @param str CharSequence whose directionality is to be estimated. 354f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * @return true if {@code str}'s estimated overall directionality is RTL. Otherwise returns 355f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * false. 356f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader */ 357f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader public boolean isRtl(CharSequence str) { 358c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio return mDefaultTextDirectionHeuristicCompat.isRtl(str, 0, str.length()); 35977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 36077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 36177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 36277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Formats a string of given directionality for use in plain-text output of the context 36377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality, so an opposite-directionality string is neither garbled nor garbles its 364b37703fef07bb1940fd9d933bb9cc5b6c3959cb2Fabrice Di Meglio * surroundings. This makes use of Unicode bidi formatting characters. 36577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p> 36677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * The algorithm: In case the given directionality doesn't match the context directionality, wraps 36777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * the string with Unicode bidi formatting characters: RLE+{@code str}+PDF for RTL text, or 36877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * LRE+{@code str}+PDF for LTR text. 36977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p> 37077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * If {@code isolate}, directionally isolates the string so that it does not garble its 37177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * surroundings. Currently, this is done by "resetting" the directionality after the string by 37277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * appending a trailing Unicode bidi mark matching the context directionality (LRM or RLM) when 373e3754c041d53f20faef1ee23f9b1b7e0c1c0f081Roozbeh Pournader * either the overall directionality or the exit directionality of the string is opposite to 374e3754c041d53f20faef1ee23f9b1b7e0c1c0f081Roozbeh Pournader * that of the context. Unless the formatter was built using 375e3754c041d53f20faef1ee23f9b1b7e0c1c0f081Roozbeh Pournader * {@link Builder#stereoReset(boolean)} with a {@code false} argument, also prepends a Unicode 376e3754c041d53f20faef1ee23f9b1b7e0c1c0f081Roozbeh Pournader * bidi mark matching the context directionality when either the overall directionality or the 377e3754c041d53f20faef1ee23f9b1b7e0c1c0f081Roozbeh Pournader * entry directionality of the string is opposite to that of the context. Note that as opposed 378e3754c041d53f20faef1ee23f9b1b7e0c1c0f081Roozbeh Pournader * to the overall directionality, the entry and exit directionalities are determined from the 379e3754c041d53f20faef1ee23f9b1b7e0c1c0f081Roozbeh Pournader * string itself. 38077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p> 38177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Does *not* do HTML-escaping. 38277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 38377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str The input string. 38477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param heuristic The algorithm to be used to estimate the string's overall direction. 38577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param isolate Whether to directionally isolate the string to prevent it from garbling the 38677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * content around it 387a404677a9abad23c78a35512cd7428f86724b30aRoozbeh Pournader * @return Input string after applying the above processing. {@code null} if {@code str} is 388a404677a9abad23c78a35512cd7428f86724b30aRoozbeh Pournader * {@code null}. 38977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 39077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String unicodeWrap(String str, TextDirectionHeuristicCompat heuristic, boolean isolate) { 391a404677a9abad23c78a35512cd7428f86724b30aRoozbeh Pournader if (str == null) return null; 392f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader return unicodeWrap((CharSequence) str, heuristic, isolate).toString(); 393f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader } 394f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader 395f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader /** 396f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * Operates like {@link #unicodeWrap(String, 397ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikas * androidx.core.text.TextDirectionHeuristicCompat, boolean)}, but takes a CharSequence 398f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * instead of a string 399f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * 400f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * @param str The input CharSequence. 401f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * @param heuristic The algorithm to be used to estimate the CharSequence's overall direction. 402ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikas * See {@link androidx.core.text.TextDirectionHeuristicsCompat} for pre-defined 403f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * heuristics. 404f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * @param isolate Whether to directionally isolate the CharSequence to prevent it from garbling 405f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * the content around it 406f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * @return Input CharSequence after applying the above processing. {@code null} if {@code str} 407f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * is {@code null}. 408f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader */ 409f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader public CharSequence unicodeWrap(CharSequence str, TextDirectionHeuristicCompat heuristic, 410f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader boolean isolate) { 411f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader if (str == null) return null; 41277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio final boolean isRtl = heuristic.isRtl(str, 0, str.length()); 413f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader SpannableStringBuilder result = new SpannableStringBuilder(); 41477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (getStereoReset() && isolate) { 41577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio result.append(markBefore(str, 41677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio isRtl ? TextDirectionHeuristicsCompat.RTL : TextDirectionHeuristicsCompat.LTR)); 41777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 418c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio if (isRtl != mIsRtlContext) { 41977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio result.append(isRtl ? RLE : LRE); 42077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio result.append(str); 42177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio result.append(PDF); 42277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } else { 42377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio result.append(str); 42477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 42577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (isolate) { 42677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio result.append(markAfter(str, 42777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio isRtl ? TextDirectionHeuristicsCompat.RTL : TextDirectionHeuristicsCompat.LTR)); 42877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 429f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader return result; 43077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 43177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 43277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 433ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikas * Operates like {@link #unicodeWrap(String, androidx.core.text.TextDirectionHeuristicCompat, boolean)}, but assumes 43477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * {@code isolate} is true. 43577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 43677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str The input string. 43777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param heuristic The algorithm to be used to estimate the string's overall direction. 43877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return Input string after applying the above processing. 43977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 44077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String unicodeWrap(String str, TextDirectionHeuristicCompat heuristic) { 44177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return unicodeWrap(str, heuristic, true /* isolate */); 44277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 44377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 44477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 445f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * Operates like {@link #unicodeWrap(CharSequence, 446ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikas * androidx.core.text.TextDirectionHeuristicCompat, boolean)}, but assumes {@code isolate} 447f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * is true. 448f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * 449f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * @param str The input CharSequence. 450f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * @param heuristic The algorithm to be used to estimate the CharSequence's overall direction. 451ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikas * See {@link androidx.core.text.TextDirectionHeuristicsCompat} for pre-defined 452f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * heuristics. 453f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * @return Input CharSequence after applying the above processing. 454f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader */ 455f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader public CharSequence unicodeWrap(CharSequence str, TextDirectionHeuristicCompat heuristic) { 456f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader return unicodeWrap(str, heuristic, true /* isolate */); 457f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader } 458f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader 459f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader /** 460ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikas * Operates like {@link #unicodeWrap(String, androidx.core.text.TextDirectionHeuristicCompat, boolean)}, but uses the 46177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * formatter's default direction estimation algorithm. 46277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 46377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str The input string. 46477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param isolate Whether to directionally isolate the string to prevent it from garbling the 46577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * content around it 46677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return Input string after applying the above processing. 46777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 46877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String unicodeWrap(String str, boolean isolate) { 469c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio return unicodeWrap(str, mDefaultTextDirectionHeuristicCompat, isolate); 47077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 47177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 47277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 473f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * Operates like {@link #unicodeWrap(CharSequence, 474ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikas * androidx.core.text.TextDirectionHeuristicCompat, boolean)}, but uses the formatter's 475f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * default direction estimation algorithm. 476f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * 477f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * @param str The input CharSequence. 478f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * @param isolate Whether to directionally isolate the CharSequence to prevent it from garbling 479f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * the content around it 480f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * @return Input CharSequence after applying the above processing. 481f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader */ 482f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader public CharSequence unicodeWrap(CharSequence str, boolean isolate) { 483f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader return unicodeWrap(str, mDefaultTextDirectionHeuristicCompat, isolate); 484f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader } 485f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader 486f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader /** 487ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikas * Operates like {@link #unicodeWrap(String, androidx.core.text.TextDirectionHeuristicCompat, boolean)}, but uses the 48877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * formatter's default direction estimation algorithm and assumes {@code isolate} is true. 48977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 49077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str The input string. 49177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return Input string after applying the above processing. 49277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 49377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String unicodeWrap(String str) { 494c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio return unicodeWrap(str, mDefaultTextDirectionHeuristicCompat, true /* isolate */); 49577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 49677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 49777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 498f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * Operates like {@link #unicodeWrap(CharSequence, 499ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikas * androidx.core.text.TextDirectionHeuristicCompat, boolean)}, but uses the formatter's 500f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * default direction estimation algorithm and assumes {@code isolate} is true. 501f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * 502f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * @param str The input CharSequence. 503f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader * @return Input CharSequence after applying the above processing. 504f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader */ 505f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader public CharSequence unicodeWrap(CharSequence str) { 506f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader return unicodeWrap(str, mDefaultTextDirectionHeuristicCompat, true /* isolate */); 507f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader } 508f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader 509f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader /** 51077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Helper method to return true if the Locale directionality is RTL. 51177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 51277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param locale The Locale whose directionality will be checked to be RTL or LTR 51377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return true if the {@code locale} directionality is RTL. False otherwise. 51477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 5158eeb6c6cdd4bdcb6807cd8152e1e36b4883a8fa5Jake Wharton static boolean isRtlLocale(Locale locale) { 51677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return (TextUtilsCompat.getLayoutDirectionFromLocale(locale) == ViewCompat.LAYOUT_DIRECTION_RTL); 51777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 51877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 51977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 52077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Enum for directionality type. 52177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 52277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final int DIR_LTR = -1; 52377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final int DIR_UNKNOWN = 0; 52477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final int DIR_RTL = +1; 52577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 52677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 52777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Returns the directionality of the last character with strong directionality in the string, or 52877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * DIR_UNKNOWN if none was encountered. For efficiency, actually scans backwards from the end of 52977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * the string. Treats a non-BN character between an LRE/RLE/LRO/RLO and its matching PDF as a 53077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * strong character, LTR after LRE/LRO, and RTL after RLE/RLO. The results are undefined for a 53177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * string containing unbalanced LRE/RLE/LRO/RLO/PDF characters. The intended use is to check 53277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * whether a logically separate item that starts with a number or a character of the string's 53377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * exit directionality and follows this string inline (not counting any neutral characters in 53477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * between) would "stick" to it in an opposite-directionality context, thus being displayed in 53577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * an incorrect position. An LRM or RLM character (the one of the context's directionality) 53677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * between the two will prevent such sticking. 53777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 53877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str the string to check. 53977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 540f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader private static int getExitDir(CharSequence str) { 54177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return new DirectionalityEstimator(str, false /* isHtml */).getExitDir(); 54277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 54377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 54477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 54577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Returns the directionality of the first character with strong directionality in the string, 54677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * or DIR_UNKNOWN if none was encountered. Treats a non-BN character between an 54777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * LRE/RLE/LRO/RLO and its matching PDF as a strong character, LTR after LRE/LRO, and RTL after 54877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * RLE/RLO. The results are undefined for a string containing unbalanced LRE/RLE/LRO/RLO/PDF 54977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * characters. The intended use is to check whether a logically separate item that ends with a 55077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * character of the string's entry directionality and precedes the string inline (not counting 55177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * any neutral characters in between) would "stick" to it in an opposite-directionality context, 55277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * thus being displayed in an incorrect position. An LRM or RLM character (the one of the 55377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * context's directionality) between the two will prevent such sticking. 55477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 55577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str the string to check. 55677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 557f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader private static int getEntryDir(CharSequence str) { 55877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return new DirectionalityEstimator(str, false /* isHtml */).getEntryDir(); 55977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 56077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 56177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 56277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * An object that estimates the directionality of a given string by various methods. 56377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 56477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 56577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static class DirectionalityEstimator { 56677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 56777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // Internal static variables and constants. 56877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 56977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 57077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Size of the bidi character class cache. The results of the Character.getDirectionality() 57177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * calls on the lowest DIR_TYPE_CACHE_SIZE codepoints are kept in an array for speed. 57277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * The 0x700 value is designed to leave all the European and Near Eastern languages in the 57377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * cache. It can be reduced to 0x180, restricting the cache to the Western European 57477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * languages. 57577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 57677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final int DIR_TYPE_CACHE_SIZE = 0x700; 57777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 57877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 57977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * The bidi character class cache. 58077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 58177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final byte DIR_TYPE_CACHE[]; 58277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 58377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio static { 58477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio DIR_TYPE_CACHE = new byte[DIR_TYPE_CACHE_SIZE]; 58577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio for (int i = 0; i < DIR_TYPE_CACHE_SIZE; i++) { 58677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio DIR_TYPE_CACHE[i] = Character.getDirectionality(i); 58777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 58877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 58977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 59077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // Internal instance variables. 59177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 59277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 59377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * The text to be scanned. 59477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 595f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader private final CharSequence text; 59677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 59777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 59877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Whether the text to be scanned is to be treated as HTML, i.e. skipping over tags and 59977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * entities when looking for the next / preceding dir type. 60077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 60177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private final boolean isHtml; 60277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 60377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 60477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * The length of the text in chars. 60577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 60677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private final int length; 60777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 60877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 60977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * The current position in the text. 61077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 61177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private int charIndex; 61277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 61377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 61477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * The char encountered by the last dirTypeForward or dirTypeBackward call. If it 61577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * encountered a supplementary codepoint, this contains a char that is not a valid 61677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * codepoint. This is ok, because this member is only used to detect some well-known ASCII 61777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * syntax, e.g. "http://" and the beginning of an HTML tag or entity. 61877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 61977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private char lastChar; 62077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 62177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 62277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Constructor. 62377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 62477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param text The string to scan. 62577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param isHtml Whether the text to be scanned is to be treated as HTML, i.e. skipping over 62677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * tags and entities. 62777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 628f695f6a4e4256edf789d3f747d1e6443e1152813Roozbeh Pournader DirectionalityEstimator(CharSequence text, boolean isHtml) { 62977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio this.text = text; 63077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio this.isHtml = isHtml; 63177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio length = text.length(); 63277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 63377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 63477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 63577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Returns the directionality of the first character with strong directionality in the 63677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * string, or DIR_UNKNOWN if none was encountered. Treats a non-BN character between an 63777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * LRE/RLE/LRO/RLO and its matching PDF as a strong character, LTR after LRE/LRO, and RTL 63877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * after RLE/RLO. The results are undefined for a string containing unbalanced 63977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * LRE/RLE/LRO/RLO/PDF characters. 64077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 64177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int getEntryDir() { 64277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // The reason for this method name, as opposed to getFirstStrongDir(), is that 64377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // "first strong" is a commonly used description of Unicode's estimation algorithm, 64477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // but the two must treat formatting characters quite differently. Thus, we are staying 64577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // away from both "first" and "last" in these method names to avoid confusion. 64677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio charIndex = 0; 64777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int embeddingLevel = 0; 64877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int embeddingLevelDir = DIR_UNKNOWN; 64977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int firstNonEmptyEmbeddingLevel = 0; 65077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio while (charIndex < length && firstNonEmptyEmbeddingLevel == 0) { 65177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio switch (dirTypeForward()) { 65277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING: 65377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE: 65477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio ++embeddingLevel; 65577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio embeddingLevelDir = DIR_LTR; 65677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 65777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING: 65877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE: 65977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio ++embeddingLevel; 66077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio embeddingLevelDir = DIR_RTL; 66177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 66277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT: 66377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio --embeddingLevel; 66477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // To restore embeddingLevelDir to its previous value, we would need a 66577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // stack, which we want to avoid. Thus, at this point we do not know the 66677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // current embedding's directionality. 66777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio embeddingLevelDir = DIR_UNKNOWN; 66877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 66977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_BOUNDARY_NEUTRAL: 67077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 67177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_LEFT_TO_RIGHT: 67277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (embeddingLevel == 0) { 67377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return DIR_LTR; 67477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 67577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio firstNonEmptyEmbeddingLevel = embeddingLevel; 67677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 67777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_RIGHT_TO_LEFT: 67877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC: 67977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (embeddingLevel == 0) { 68077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return DIR_RTL; 68177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 68277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio firstNonEmptyEmbeddingLevel = embeddingLevel; 68377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 68477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio default: 68577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio firstNonEmptyEmbeddingLevel = embeddingLevel; 68677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 68777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 68877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 68977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 69077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // We have either found a non-empty embedding or scanned the entire string finding 69177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // neither a non-empty embedding nor a strong character outside of an embedding. 69277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (firstNonEmptyEmbeddingLevel == 0) { 69377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // We have not found a non-empty embedding. Thus, the string contains neither a 69477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // non-empty embedding nor a strong character outside of an embedding. 69577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return DIR_UNKNOWN; 69677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 69777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 69877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // We have found a non-empty embedding. 69977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (embeddingLevelDir != DIR_UNKNOWN) { 70077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // We know the directionality of the non-empty embedding. 70177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return embeddingLevelDir; 70277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 70377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 70477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // We do not remember the directionality of the non-empty embedding we found. So, we go 70577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // backwards to find the start of the non-empty embedding and get its directionality. 70677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio while (charIndex > 0) { 70777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio switch (dirTypeBackward()) { 70877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING: 70977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE: 71077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (firstNonEmptyEmbeddingLevel == embeddingLevel) { 71177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return DIR_LTR; 71277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 71377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio --embeddingLevel; 71477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 71577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING: 71677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE: 71777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (firstNonEmptyEmbeddingLevel == embeddingLevel) { 71877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return DIR_RTL; 71977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 72077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio --embeddingLevel; 72177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 72277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT: 72377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio ++embeddingLevel; 72477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 72577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 72677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 72777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // We should never get here. 72877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return DIR_UNKNOWN; 72977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 73077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 73177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 73277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Returns the directionality of the last character with strong directionality in the 73377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * string, or DIR_UNKNOWN if none was encountered. For efficiency, actually scans backwards 73477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * from the end of the string. Treats a non-BN character between an LRE/RLE/LRO/RLO and its 73577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * matching PDF as a strong character, LTR after LRE/LRO, and RTL after RLE/RLO. The results 73677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * are undefined for a string containing unbalanced LRE/RLE/LRO/RLO/PDF characters. 73777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 73877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int getExitDir() { 73977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // The reason for this method name, as opposed to getLastStrongDir(), is that "last 74077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // strong" sounds like the exact opposite of "first strong", which is a commonly used 74177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // description of Unicode's estimation algorithm (getUnicodeDir() above), but the two 74277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // must treat formatting characters quite differently. Thus, we are staying away from 74377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // both "first" and "last" in these method names to avoid confusion. 74477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio charIndex = length; 74577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int embeddingLevel = 0; 74677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int lastNonEmptyEmbeddingLevel = 0; 74777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio while (charIndex > 0) { 74877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio switch (dirTypeBackward()) { 74977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_LEFT_TO_RIGHT: 75077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (embeddingLevel == 0) { 75177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return DIR_LTR; 75277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 75377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastNonEmptyEmbeddingLevel == 0) { 75477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio lastNonEmptyEmbeddingLevel = embeddingLevel; 75577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 75677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 75777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING: 75877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE: 75977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastNonEmptyEmbeddingLevel == embeddingLevel) { 76077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return DIR_LTR; 76177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 76277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio --embeddingLevel; 76377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 76477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_RIGHT_TO_LEFT: 76577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC: 76677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (embeddingLevel == 0) { 76777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return DIR_RTL; 76877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 76977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastNonEmptyEmbeddingLevel == 0) { 77077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio lastNonEmptyEmbeddingLevel = embeddingLevel; 77177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 77277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 77377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING: 77477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE: 77577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastNonEmptyEmbeddingLevel == embeddingLevel) { 77677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return DIR_RTL; 77777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 77877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio --embeddingLevel; 77977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 78077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT: 78177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio ++embeddingLevel; 78277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 78377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_BOUNDARY_NEUTRAL: 78477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 78577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio default: 78677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastNonEmptyEmbeddingLevel == 0) { 78777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio lastNonEmptyEmbeddingLevel = embeddingLevel; 78877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 78977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 79077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 79177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 79277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return DIR_UNKNOWN; 79377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 79477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 79577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // Internal methods 79677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 79777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 79877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Gets the bidi character class, i.e. Character.getDirectionality(), of a given char, using 79977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * a cache for speed. Not designed for supplementary codepoints, whose results we do not 80077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * cache. 80177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 80277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static byte getCachedDirectionality(char c) { 80377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return c < DIR_TYPE_CACHE_SIZE ? DIR_TYPE_CACHE[c] : Character.getDirectionality(c); 80477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 80577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 80677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 80777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Returns the Character.DIRECTIONALITY_... value of the next codepoint and advances 80877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * charIndex. If isHtml, and the codepoint is '<' or '&', advances through the tag/entity, 80977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * and returns Character.DIRECTIONALITY_WHITESPACE. For an entity, it would be best to 81077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * figure out the actual character, and return its dirtype, but treating it as whitespace is 81177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * good enough for our purposes. 81277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 81377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @throws java.lang.IndexOutOfBoundsException if called when charIndex >= length or < 0. 81477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 81577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio byte dirTypeForward() { 81677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio lastChar = text.charAt(charIndex); 81777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (Character.isHighSurrogate(lastChar)) { 81877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int codePoint = Character.codePointAt(text, charIndex); 81977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio charIndex += Character.charCount(codePoint); 82077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return Character.getDirectionality(codePoint); 82177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 82277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio charIndex++; 82377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio byte dirType = getCachedDirectionality(lastChar); 82477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (isHtml) { 82577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // Process tags and entities. 82677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastChar == '<') { 82777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio dirType = skipTagForward(); 82877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } else if (lastChar == '&') { 82977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio dirType = skipEntityForward(); 83077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 83177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 83277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return dirType; 83377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 83477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 83577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 83677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Returns the Character.DIRECTIONALITY_... value of the preceding codepoint and advances 83777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * charIndex backwards. If isHtml, and the codepoint is the end of a complete HTML tag or 83877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * entity, advances over the whole tag/entity and returns 83977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Character.DIRECTIONALITY_WHITESPACE. For an entity, it would be best to figure out the 84077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * actual character, and return its dirtype, but treating it as whitespace is good enough 84177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * for our purposes. 84277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 84377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @throws java.lang.IndexOutOfBoundsException if called when charIndex > length or <= 0. 84477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 84577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio byte dirTypeBackward() { 84677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio lastChar = text.charAt(charIndex - 1); 84777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (Character.isLowSurrogate(lastChar)) { 84877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int codePoint = Character.codePointBefore(text, charIndex); 84977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio charIndex -= Character.charCount(codePoint); 85077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return Character.getDirectionality(codePoint); 85177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 85277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio charIndex--; 85377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio byte dirType = getCachedDirectionality(lastChar); 85477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (isHtml) { 85577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // Process tags and entities. 85677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastChar == '>') { 85777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio dirType = skipTagBackward(); 85877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } else if (lastChar == ';') { 85977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio dirType = skipEntityBackward(); 86077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 86177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 86277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return dirType; 86377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 86477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 86577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 86677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Advances charIndex forward through an HTML tag (after the opening < has already been 86777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * read) and returns Character.DIRECTIONALITY_WHITESPACE. If there is no matching >, 86877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * does not change charIndex and returns Character.DIRECTIONALITY_OTHER_NEUTRALS (for the 86977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * < that hadn't been part of a tag after all). 87077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 87177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private byte skipTagForward() { 87277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int initialCharIndex = charIndex; 87377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio while (charIndex < length) { 87477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio lastChar = text.charAt(charIndex++); 87577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastChar == '>') { 87677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // The end of the tag. 87777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return Character.DIRECTIONALITY_WHITESPACE; 87877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 87977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastChar == '"' || lastChar == '\'') { 88077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // Skip over a quoted attribute value inside the tag. 88177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio char quote = lastChar; 88277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio while (charIndex < length && (lastChar = text.charAt(charIndex++)) != quote) {} 88377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 88477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 88577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // The original '<' wasn't the start of a tag after all. 88677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio charIndex = initialCharIndex; 88777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio lastChar = '<'; 88877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return Character.DIRECTIONALITY_OTHER_NEUTRALS; 88977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 89077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 89177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 89277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Advances charIndex backward through an HTML tag (after the closing > has already been 89377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * read) and returns Character.DIRECTIONALITY_WHITESPACE. If there is no matching <, does 89477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * not change charIndex and returns Character.DIRECTIONALITY_OTHER_NEUTRALS (for the > 89577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * that hadn't been part of a tag after all). Nevertheless, the running time for calling 89677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * skipTagBackward() in a loop remains linear in the size of the text, even for a text like 89777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * ">>>>", because skipTagBackward() also stops looking for a matching < 89877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * when it encounters another >. 89977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 90077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private byte skipTagBackward() { 90177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int initialCharIndex = charIndex; 90277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio while (charIndex > 0) { 90377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio lastChar = text.charAt(--charIndex); 90477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastChar == '<') { 90577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // The start of the tag. 90677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return Character.DIRECTIONALITY_WHITESPACE; 90777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 90877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastChar == '>') { 90977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 91077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 91177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastChar == '"' || lastChar == '\'') { 91277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // Skip over a quoted attribute value inside the tag. 91377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio char quote = lastChar; 91477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio while (charIndex > 0 && (lastChar = text.charAt(--charIndex)) != quote) {} 91577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 91677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 91777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // The original '>' wasn't the end of a tag after all. 91877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio charIndex = initialCharIndex; 91977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio lastChar = '>'; 92077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return Character.DIRECTIONALITY_OTHER_NEUTRALS; 92177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 92277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 92377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 92477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Advances charIndex forward through an HTML character entity tag (after the opening 92577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * & has already been read) and returns Character.DIRECTIONALITY_WHITESPACE. It would be 92677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * best to figure out the actual character and return its dirtype, but this is good enough. 92777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 92877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private byte skipEntityForward() { 92977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio while (charIndex < length && (lastChar = text.charAt(charIndex++)) != ';') {} 93077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return Character.DIRECTIONALITY_WHITESPACE; 93177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 93277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 93377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 93477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Advances charIndex backward through an HTML character entity tag (after the closing ; 93577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * has already been read) and returns Character.DIRECTIONALITY_WHITESPACE. It would be best 93677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * to figure out the actual character and return its dirtype, but this is good enough. 93777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * If there is no matching &, does not change charIndex and returns 93877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Character.DIRECTIONALITY_OTHER_NEUTRALS (for the ';' that did not start an entity after 93977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * all). Nevertheless, the running time for calling skipEntityBackward() in a loop remains 94077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * linear in the size of the text, even for a text like ";;;;;;;", because skipTagBackward() 94177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * also stops looking for a matching & when it encounters another ;. 94277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 94377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private byte skipEntityBackward() { 94477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int initialCharIndex = charIndex; 94577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio while (charIndex > 0) { 94677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio lastChar = text.charAt(--charIndex); 94777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastChar == '&') { 94877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return Character.DIRECTIONALITY_WHITESPACE; 94977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 95077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastChar == ';') { 95177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 95277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 95377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 95477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio charIndex = initialCharIndex; 95577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio lastChar = ';'; 95677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return Character.DIRECTIONALITY_OTHER_NEUTRALS; 95777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 95877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 9598eeb6c6cdd4bdcb6807cd8152e1e36b4883a8fa5Jake Wharton} 960