BidiFormatter.java revision c92d608279e5716a176e142abcd5e1b2148a0680
177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio/* 277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Copyright (C) 2013 The Android Open Source Project 377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Licensed under the Apache License, Version 2.0 (the "License"); 577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * you may not use this file except in compliance with the License. 677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * You may obtain a copy of the License at 777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * http://www.apache.org/licenses/LICENSE-2.0 977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 1077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Unless required by applicable law or agreed to in writing, software 1177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * distributed under the License is distributed on an "AS IS" BASIS, 1277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * See the License for the specific language governing permissions and 1477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * limitations under the License. 1577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 1677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 1777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Megliopackage android.support.v4.text.bidi; 1877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 1977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglioimport android.support.v4.text.TextDirectionHeuristicCompat; 2077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglioimport android.support.v4.text.TextDirectionHeuristicsCompat; 2177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglioimport android.support.v4.text.TextUtilsCompat; 2277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglioimport android.support.v4.view.ViewCompat; 2377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 2477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglioimport java.util.Locale; 2577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 2677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglioimport static android.support.v4.text.TextDirectionHeuristicsCompat.FIRSTSTRONG_LTR; 2777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 2877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio/** 2977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Utility class for formatting text for display in a potentially opposite-directionality context 3077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * without garbling. The directionality of the context is set at formatter creation and the 3177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality of the text can be either estimated or passed in when known. Provides the 3277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * following functionality: 3377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p> 3477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 1. Bidi Wrapping 3577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * When text in one language is mixed into a document in another, opposite-directionality language, 3677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * e.g. when an English business name is embedded in a Hebrew web page, both the inserted string 3777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * and the text surrounding it may be displayed incorrectly unless the inserted string is explicitly 3877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * separated from the surrounding text in a "wrapper" that: 3977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p> 4077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * - Declares its directionality so that the string is displayed correctly. This can be done in HTML 4177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * markup (e.g. a 'span dir="rtl"' element) by {@link #spanWrap} and similar methods, or - only in 4277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * contexts where markup can't be used - in Unicode bidi formatting codes by {@link #unicodeWrap} 4377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * and similar methods. 4477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p> 4577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * - Isolates the string's directionality, so it does not unduly affect the surrounding content. 4677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Currently, this can only be done using invisible Unicode characters of the same direction as 4777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * the context (LRM or RLM) in addition to the directionality declaration above, thus "resetting" 4877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * the directionality to that of the context. The "reset" may need to be done at both ends of the 4977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * string. Without "reset" after the string, the string will "stick" to a number or logically 5077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * separate opposite-direction text that happens to follow it in-line (even if separated by 5177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * neutral content like spaces and punctuation). Without "reset" before the string, the same can 5277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * happen there, but only with more opposite-direction text, not a number. One approach is to 5377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * "reset" the direction only after each string, on the theory that if the preceding opposite- 5477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * direction text is itself bidi-wrapped, the "reset" after it will prevent the sticking. (Doing 5577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * the "reset" only before each string definitely does not work because we do not want to require 5677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * bidi-wrapping numbers, and a bidi-wrapped opposite-direction string could be followed by a 5777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * number.) Still, the safest policy is to do the "reset" on both ends of each string, since RTL 5877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * message translations often contain untranslated Latin-script brand names and technical terms, 5977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * and one of these can be followed by a bidi-wrapped inserted value. On the other hand, when one 6077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * has such a message, it is best to do the "reset" manually in the message translation itself, 6177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * since the message's opposite-direction text could be followed by an inserted number, which we 6277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * would not bidi-wrap anyway. Thus, "reset" only after the string is the current default. In an 6377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * alternative to "reset", recent additions to the HTML, CSS, and Unicode standards allow the 6477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * isolation to be part of the directionality declaration. This form of isolation is better than 6577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * "reset" because it takes less space, does not require knowing the context directionality, has a 6677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * gentler effect than "reset", and protects both ends of the string. However, we do not yet allow 6777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * using it because required platforms do not yet support it. 6877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p> 6977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Providing these wrapping services is the basic purpose of the bidi formatter. 7077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p> 7177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 2. Directionality estimation 7277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * How does one know whether a string about to be inserted into surrounding text has the same 7377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality? Well, in many cases, one knows that this must be the case when writing the code 7477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * doing the insertion, e.g. when a localized message is inserted into a localized page. In such 7577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * cases there is no need to involve the bidi formatter at all. In some other cases, it need not be 7677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * the same as the context, but is either constant (e.g. urls are always LTR) or otherwise known. 7777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * In the remaining cases, e.g. when the string is user-entered or comes from a database, the 7877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * language of the string (and thus its directionality) is not known a priori, and must be 7977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * estimated at run-time. The bidi formatter can do this automatically using the default 8077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * first-strong estimation algorithm. It can also be configured to use a custom directionality 8177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * estimation object. 8277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p> 8377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 3. Escaping 8477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * When wrapping plain text - i.e. text that is not already HTML or HTML-escaped - in HTML markup, 8577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * the text must first be HTML-escaped to prevent XSS attacks and other nasty business. This of 8677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * course is always true, but the escaping can not be done after the string has already been wrapped 8777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * in markup, so the bidi formatter also serves as a last chance and includes escaping services. 8877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p> 8977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Thus, in a single call, the formatter will escape the input string as specified, determine its 9077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality, and wrap it as necessary. It is then up to the caller to insert the return value 9177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * in the output. 9277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 9377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Megliopublic final class BidiFormatter { 9477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 9577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 9677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * The default text direction heuristic. 9777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 9877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static TextDirectionHeuristicCompat DEFAULT_TEXT_DIRECTION_HEURISTIC = FIRSTSTRONG_LTR; 9977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 10077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 10177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Unicode "Left-To-Right Embedding" (LRE) character. 10277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 10377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final char LRE = '\u202A'; 10477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 10577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 10677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Unicode "Right-To-Left Embedding" (RLE) character. 10777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 10877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final char RLE = '\u202B'; 10977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 11077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 11177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Unicode "Pop Directional Formatting" (PDF) character. 11277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 11377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final char PDF = '\u202C'; 11477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 11577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 11677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Unicode "Left-To-Right Mark" (LRM) character. 11777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 11877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final char LRM = '\u200E'; 11977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 12077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /* 12177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Unicode "Right-To-Left Mark" (RLM) character. 12277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 12377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final char RLM = '\u200F'; 12477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 12577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /* 12677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * String representation of LRM 12777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 12877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final String LRM_STRING = Character.toString(LRM); 12977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 13077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /* 13177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * String representation of RLM 13277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 13377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final String RLM_STRING = Character.toString(RLM); 13477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 13577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 13677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * "ltr" string constant. 13777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 13877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final String LTR_STRING = "ltr"; 13977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 14077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 14177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * "rtl" string constant. 14277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 14377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final String RTL_STRING = "rtl"; 14477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 14577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 14677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * "dir=\"ltr\"" string constant. 14777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 14877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final String DIR_LTR_STRING = "dir=\"ltr\""; 14977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 15077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 15177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * "dir=\"rtl\"" string constant. 15277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 15377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final String DIR_RTL_STRING = "dir=\"rtl\""; 15477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 15577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 15677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * "right" string constant. 15777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 15877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final String RIGHT = "right"; 15977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 16077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 16177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * "left" string constant. 16277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 16377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final String LEFT = "left"; 16477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 16577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 16677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Empty string constant. 16777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 16877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final String EMPTY_STRING = ""; 16977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 17077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 17177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * A class for building a BidiFormatter with non-default options. 17277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 17377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public static final class Builder { 174c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio private boolean mIsRtlContext; 175c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio private int mFlags; 176c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio private TextDirectionHeuristicCompat mTextDirectionHeuristicCompat; 17777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 17877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 17977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Constructor. 18077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 18177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 18277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public Builder() { 18377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio initialize(isRtlLocale(Locale.getDefault())); 18477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 18577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 18677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 18777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Constructor. 18877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 18977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param rtlContext Whether the context directionality is RTL. 19077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 19177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public Builder(boolean rtlContext) { 19277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio initialize(rtlContext); 19377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 19477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 19577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 19677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Constructor. 19777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 19877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param locale The context locale. 19977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 20077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public Builder(Locale locale) { 20177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio initialize(isRtlLocale(locale)); 20277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 20377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 20477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 20577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Initializes the builder with the given context directionality and default options. 20677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 20777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param isRtlContext Whether the context is RTL or not. 20877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 20977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private void initialize(boolean isRtlContext) { 210c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio mIsRtlContext = isRtlContext; 211c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio mTextDirectionHeuristicCompat = DEFAULT_TEXT_DIRECTION_HEURISTIC; 212c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio mFlags = DEFAULT_FLAGS; 21377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 21477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 21577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 21677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Specifies whether the BidiFormatter to be built should also "reset" directionality before 21777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * a string being bidi-wrapped, not just after it. The default is false. 21877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 21977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public Builder stereoReset(boolean stereoReset) { 22077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (stereoReset) { 221c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio mFlags |= FLAG_STEREO_RESET; 22277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } else { 223c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio mFlags &= ~FLAG_STEREO_RESET; 22477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 22577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return this; 22677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 22777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 22877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 22977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Specifies the default directionality estimation algorithm to be used by the BidiFormatter. 23077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * By default, uses the first-strong heuristic. 23177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 23277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param heuristic the {@code TextDirectionHeuristic} to use. 23377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return the builder itself. 23477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 23577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public Builder setTextDirectionHeuristic(TextDirectionHeuristicCompat heuristic) { 236c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio mTextDirectionHeuristicCompat = heuristic; 23777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return this; 23877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 23977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 24077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static BidiFormatter getDefaultInstanceFromContext(boolean isRtlContext) { 24177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return isRtlContext ? DEFAULT_RTL_INSTANCE : DEFAULT_LTR_INSTANCE; 24277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 24377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 24477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 24577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return A BidiFormatter with the specified options. 24677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 24777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public BidiFormatter build() { 248c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio if (mFlags == DEFAULT_FLAGS && 249c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio mTextDirectionHeuristicCompat == DEFAULT_TEXT_DIRECTION_HEURISTIC) { 250c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio return getDefaultInstanceFromContext(mIsRtlContext); 25177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 252c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio return new BidiFormatter(mIsRtlContext, mFlags, mTextDirectionHeuristicCompat); 25377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 25477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 25577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 25677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // 25777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final int FLAG_STEREO_RESET = 2; 25877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final int DEFAULT_FLAGS = FLAG_STEREO_RESET; 25977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 26077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final BidiFormatter DEFAULT_LTR_INSTANCE = new BidiFormatter( 26177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio false /* LTR context */, 26277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio DEFAULT_FLAGS, 26377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio DEFAULT_TEXT_DIRECTION_HEURISTIC); 26477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 26577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final BidiFormatter DEFAULT_RTL_INSTANCE = new BidiFormatter( 26677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio true /* RTL context */, 26777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio DEFAULT_FLAGS, 26877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio DEFAULT_TEXT_DIRECTION_HEURISTIC); 26977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 270c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio private final boolean mIsRtlContext; 271c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio private final int mFlags; 272c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio private final TextDirectionHeuristicCompat mDefaultTextDirectionHeuristicCompat; 27377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 27477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 27577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Factory for creating an instance of BidiFormatter given the context directionality. 27677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 27777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param rtlContext Whether the context directionality is RTL. 27877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 27977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public static BidiFormatter getInstance(boolean rtlContext) { 28077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return new Builder(rtlContext).build(); 28177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 28277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 28377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 28477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Factory for creating an instance of BidiFormatter given the context locale. 28577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 28677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param locale The context locale. 28777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 28877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public static BidiFormatter getInstance(Locale locale) { 28977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return new Builder(locale).build(); 29077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 29177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 29277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 29377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param isRtlContext Whether the context directionality is RTL or not. 29477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param flags The option flags. 29577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param heuristic The default text direction heuristic. 29677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 29777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private BidiFormatter(boolean isRtlContext, int flags, TextDirectionHeuristicCompat heuristic) { 298c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio mIsRtlContext = isRtlContext; 299c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio mFlags = flags; 300c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio mDefaultTextDirectionHeuristicCompat = heuristic; 30177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 30277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 30377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 30477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return Whether the context directionality is RTL 30577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 30677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public boolean isRtlContext() { 307c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio return mIsRtlContext; 30877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 30977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 31077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 31177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return Whether directionality "reset" should also be done before a string being 31277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * bidi-wrapped, not just after it. 31377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 31477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public boolean getStereoReset() { 315c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio return (mFlags & FLAG_STEREO_RESET) != 0; 31677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 31777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 31877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 31977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Returns "rtl" if {@code str}'s estimated directionality is RTL, and "ltr" if it is LTR. 32077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 32177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str String whose directionality is to be estimated. 32277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return "rtl" if {@code str}'s estimated directionality is RTL, and "ltr" otherwise. 32377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 32477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String dirAttrValue(String str) { 32577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return dirAttrValue(isRtl(str)); 32677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 32777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 32877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 32977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Operates like {@link #dirAttrValue(String)}, but uses a given heuristic to estimate the 33077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * {@code str}'s directionality. 33177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 33277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str String whose directionality is to be estimated. 33377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s 33477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality. 33577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return "rtl" if {@code str}'s estimated directionality is RTL, and "ltr" otherwise. 33677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 33777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String dirAttrValue(String str, TextDirectionHeuristicCompat heuristic) { 33877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return dirAttrValue(heuristic.isRtl(str, 0, str.length())); 33977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 34077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 34177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 34277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Returns "rtl" if the given directionality is RTL, and "ltr" if it is LTR. 34377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 34477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param isRtl Whether the directionality is RTL or not. 34577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return "rtl" if the given directionality is RTL, and "ltr" otherwise. 34677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 34777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String dirAttrValue(boolean isRtl) { 34877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return isRtl ? RTL_STRING : LTR_STRING; 34977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 35077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 35177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 35277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Returns "dir=\"ltr\"" or "dir=\"rtl\"", depending on {@code str}'s estimated directionality, 35377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * if it is not the same as the context directionality. Otherwise, returns the empty string. 35477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 35577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str String whose directionality is to be estimated. 35677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return "dir=\"rtl\"" for RTL text in non-RTL context; "dir=\"ltr\"" for LTR text in non-LTR 35777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * context; else, the empty string. 35877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 35977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String dirAttr(String str) { 36077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return dirAttr(isRtl(str)); 36177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 36277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 36377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 36477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Operates like {@link #dirAttr(String)}, but uses a given heuristic to estimate the 36577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * {@code str}'s directionality. 36677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 36777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str String whose directionality is to be estimated. 36877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s 36977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality. 37077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return "dir=\"rtl\"" for RTL text in non-RTL context; "dir=\"ltr\"" for LTR text in non-LTR 37177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * context; else, the empty string. 37277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 37377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String dirAttr(String str, TextDirectionHeuristicCompat heuristic) { 37477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return dirAttr(heuristic.isRtl(str, 0, str.length())); 37577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 37677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 37777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 37877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Returns "dir=\"ltr\"" or "dir=\"rtl\"", depending on the given directionality, if it is not 37977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * the same as the context directionality. Otherwise, returns the empty string. 38077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 38177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param isRtl Whether the directionality is RTL or not 38277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return "dir=\"rtl\"" for RTL text in non-RTL context; "dir=\"ltr\"" for LTR text in non-LTR 38377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * context; else, the empty string. 38477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 38577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String dirAttr(boolean isRtl) { 386c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio return (isRtl != mIsRtlContext) ? (isRtl ? DIR_RTL_STRING : DIR_LTR_STRING) : EMPTY_STRING; 38777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 38877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 38977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 39077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Returns a Unicode bidi mark matching the context directionality (LRM or RLM) if either the 39177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * overall or the exit directionality of a given string is opposite to the context directionality. 39277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Putting this after the string (including its directionality declaration wrapping) prevents it 39377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * from "sticking" to other opposite-directionality text or a number appearing after it inline 39477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * with only neutral content in between. Otherwise returns the empty string. While the exit 39577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality is determined by scanning the end of the string, the overall directionality is 39677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * given explicitly in {@code dir}. 39777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 39877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str String after which the mark may need to appear. 39977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context; 40077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * else, the empty string. 40177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 40277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String markAfter(String str) { 403c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio return markAfter(str, mDefaultTextDirectionHeuristicCompat); 40477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 40577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 40677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 40777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Operates like {@link #markAfter(String)}, but uses a given heuristic to estimate the 40877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * {@code str}'s directionality. 40977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 41077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str String after which the mark may need to appear. 41177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s 41277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality. 41377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context; 41477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * else, the empty string. 41577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 41677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String markAfter(String str, TextDirectionHeuristicCompat heuristic) { 41777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio final boolean isRtl = heuristic.isRtl(str, 0, str.length()); 41877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // getExitDir() is called only if needed (short-circuit). 419c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio if (!mIsRtlContext && (isRtl || getExitDir(str) == DIR_RTL)) { 42077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return LRM_STRING; 42177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 422c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio if (mIsRtlContext && (!isRtl || getExitDir(str) == DIR_LTR)) { 42377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return RLM_STRING; 42477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 42577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return EMPTY_STRING; 42677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 42777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 42877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 42977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Returns a Unicode bidi mark matching the context directionality (LRM or RLM) if either the 43077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * overall or the entry directionality of a given string is opposite to the context 43177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality. Putting this before the string (including its directionality declaration 43277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * wrapping) prevents it from "sticking" to other opposite-directionality text appearing before it 43377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * inline with only neutral content in between. Otherwise returns the empty string. While the 43477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * entry directionality is determined by scanning the beginning of the string, the overall 43577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality is given explicitly in {@code dir}. 43677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 43777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str String before which the mark may need to appear. 43877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context; 43977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * else, the empty string. 44077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 44177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String markBefore(String str) { 442c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio return markBefore(str, mDefaultTextDirectionHeuristicCompat); 44377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 44477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 44577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 44677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Operates like {@link #markBefore(String)}, but uses a given heuristic to estimate the 44777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * {@code str}'s directionality. 44877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 44977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str String before which the mark may need to appear. 45077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s 45177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality. 45277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context; 45377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * else, the empty string. 45477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 45577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String markBefore(String str, TextDirectionHeuristicCompat heuristic) { 45677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio final boolean isRtl = heuristic.isRtl(str, 0, str.length()); 45777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // getEntryDir() is called only if needed (short-circuit). 458c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio if (!mIsRtlContext && (isRtl || getEntryDir(str) == DIR_RTL)) { 45977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return LRM_STRING; 46077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 461c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio if (mIsRtlContext && (!isRtl || getEntryDir(str) == DIR_LTR)) { 46277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return RLM_STRING; 46377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 46477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return EMPTY_STRING; 46577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 46677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 46777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 46877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Returns the Unicode bidi mark matching the context directionality (LRM for LTR context 46977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality, RLM for RTL context directionality). 47077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 47177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String mark() { 472c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio return mIsRtlContext ? RLM_STRING : LRM_STRING; 47377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 47477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 47577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 47677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Returns "right" for RTL context directionality. Otherwise for LTR context directionality 47777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * returns "left". 47877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 47977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String startEdge() { 480c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio return mIsRtlContext ? RIGHT : LEFT; 48177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 48277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 48377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 48477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Returns "left" for RTL context directionality. Otherwise for LTR context directionality 48577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * returns "right". 48677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 48777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String endEdge() { 488c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio return mIsRtlContext ? LEFT : RIGHT; 48977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 49077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 49177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 49277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Estimates the directionality of a string using the default text direction heuristic. 49377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 49477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str String whose directionality is to be estimated. 49577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return true if {@code str}'s estimated overall directionality is RTL. Otherwise returns 49677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * false. 49777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 49877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public boolean isRtl(String str) { 499c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio return mDefaultTextDirectionHeuristicCompat.isRtl(str, 0, str.length()); 50077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 50177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 50277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 50377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Formats a given string of unknown directionality for use in HTML output of the context 50477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality, so an opposite-directionality string is neither garbled nor garbles its 50577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * surroundings. 50677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p> 50777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * The algorithm: estimates the directionality of the given string using the given heuristic. 50877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * If the directionality is known, pass TextDirectionHeuristics.LTR or RTL for heuristic. 50977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * In case its directionality doesn't match the context directionality, wraps it with a 'span' 51077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * element and adds a "dir" attribute (either 'dir=\"rtl\"' or 'dir=\"ltr\"'). 51177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p> 51277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * If {@code isolate}, directionally isolates the string so that it does not garble its 51377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * surroundings. Currently, this is done by "resetting" the directionality after the string by 51477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * appending a trailing Unicode bidi mark matching the context directionality (LRM or RLM) when 51577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * either the overall directionality or the exit directionality of the string is opposite to that 51677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * of the context. If the formatter was built using {@link Builder#stereoReset(boolean)} and 51777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * passing "true" as an argument, also prepends a Unicode bidi mark matching the context 51877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality when either the overall directionality or the entry directionality of the 51977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * string is opposite to that of the context. 52077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p> 52177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 52277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str The input string. 52377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param heuristic The algorithm to be used to estimate the string's overall direction. 52477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param isolate Whether to directionally isolate the string to prevent it from garbling the 52577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * content around it. 52677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return Input string after applying the above processing. 52777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 52877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String spanWrap(String str, TextDirectionHeuristicCompat heuristic, boolean isolate) { 52977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio final boolean isRtl = heuristic.isRtl(str, 0, str.length()); 53077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio String origStr = str; 53177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio str = TextUtilsCompat.htmlEncode(str); 53277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 53377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio StringBuilder result = new StringBuilder(); 53477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (getStereoReset() && isolate) { 53577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio result.append(markBefore(origStr, 53677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio isRtl ? TextDirectionHeuristicsCompat.RTL : TextDirectionHeuristicsCompat.LTR)); 53777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 538c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio if (isRtl != mIsRtlContext) { 53977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio result.append("<span ").append(dirAttr(isRtl)).append('>').append(str).append("</span>"); 54077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } else { 54177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio result.append(str); 54277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 54377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (isolate) { 54477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio result.append(markAfter(origStr, 54577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio isRtl ? TextDirectionHeuristicsCompat.RTL : TextDirectionHeuristicsCompat.LTR)); 54677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 54777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return result.toString(); 54877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 54977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 55077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 55177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Operates like {@link #spanWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but assumes 55277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * {@code isolate} is true. 55377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 55477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str The input string. 55577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param heuristic The algorithm to be used to estimate the string's overall direction. 55677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return Input string after applying the above processing. 55777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 55877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String spanWrap(String str, TextDirectionHeuristicCompat heuristic) { 55977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return spanWrap(str, heuristic, true /* isolate */); 56077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 56177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 56277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 56377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Operates like {@link #spanWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but uses the 56477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * formatter's default direction estimation algorithm. 56577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 56677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str The input string. 56777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param isolate Whether to directionally isolate the string to prevent it from garbling the 56877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * content around it 56977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return Input string after applying the above processing. 57077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 57177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String spanWrap(String str, boolean isolate) { 572c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio return spanWrap(str, mDefaultTextDirectionHeuristicCompat, isolate); 57377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 57477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 57577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 57677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Operates like {@link #spanWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but uses the 57777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * formatter's default direction estimation algorithm and assumes {@code isolate} is true. 57877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 57977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str The input string. 58077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return Input string after applying the above processing. 58177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 58277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String spanWrap(String str) { 583c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio return spanWrap(str, mDefaultTextDirectionHeuristicCompat, true /* isolate */); 58477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 58577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 58677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 58777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Formats a string of given directionality for use in plain-text output of the context 58877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality, so an opposite-directionality string is neither garbled nor garbles its 58977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * surroundings. As opposed to {@link #spanWrap}, this makes use of Unicode bidi 59077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * formatting characters. In HTML, its *only* valid use is inside of elements that do not allow 59177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * markup, e.g. the 'option' and 'title' elements. 59277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p> 59377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * The algorithm: In case the given directionality doesn't match the context directionality, wraps 59477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * the string with Unicode bidi formatting characters: RLE+{@code str}+PDF for RTL text, or 59577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * LRE+{@code str}+PDF for LTR text. 59677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p> 59777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * If {@code isolate}, directionally isolates the string so that it does not garble its 59877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * surroundings. Currently, this is done by "resetting" the directionality after the string by 59977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * appending a trailing Unicode bidi mark matching the context directionality (LRM or RLM) when 60077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * either the overall directionality or the exit directionality of the string is opposite to that 60177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * of the context. If the formatter was built using {@link Builder#stereoReset(boolean)} and 60277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * passing "true" as an argument, also prepends a Unicode bidi mark matching the context 60377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality when either the overall directionality or the entry directionality of the 60477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * string is opposite to that of the context. Note that as opposed to the overall 60577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * directionality, the entry and exit directionalities are determined from the string itself. 60677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * <p> 60777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Does *not* do HTML-escaping. 60877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 60977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str The input string. 61077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param heuristic The algorithm to be used to estimate the string's overall direction. 61177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param isolate Whether to directionally isolate the string to prevent it from garbling the 61277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * content around it 61377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return Input string after applying the above processing. 61477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 61577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String unicodeWrap(String str, TextDirectionHeuristicCompat heuristic, boolean isolate) { 61677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio final boolean isRtl = heuristic.isRtl(str, 0, str.length()); 61777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio StringBuilder result = new StringBuilder(); 61877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (getStereoReset() && isolate) { 61977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio result.append(markBefore(str, 62077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio isRtl ? TextDirectionHeuristicsCompat.RTL : TextDirectionHeuristicsCompat.LTR)); 62177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 622c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio if (isRtl != mIsRtlContext) { 62377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio result.append(isRtl ? RLE : LRE); 62477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio result.append(str); 62577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio result.append(PDF); 62677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } else { 62777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio result.append(str); 62877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 62977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (isolate) { 63077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio result.append(markAfter(str, 63177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio isRtl ? TextDirectionHeuristicsCompat.RTL : TextDirectionHeuristicsCompat.LTR)); 63277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 63377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return result.toString(); 63477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 63577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 63677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 63777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Operates like {@link #unicodeWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but assumes 63877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * {@code isolate} is true. 63977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 64077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str The input string. 64177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param heuristic The algorithm to be used to estimate the string's overall direction. 64277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return Input string after applying the above processing. 64377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 64477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String unicodeWrap(String str, TextDirectionHeuristicCompat heuristic) { 64577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return unicodeWrap(str, heuristic, true /* isolate */); 64677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 64777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 64877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 64977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Operates like {@link #unicodeWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but uses the 65077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * formatter's default direction estimation algorithm. 65177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 65277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str The input string. 65377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param isolate Whether to directionally isolate the string to prevent it from garbling the 65477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * content around it 65577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return Input string after applying the above processing. 65677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 65777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String unicodeWrap(String str, boolean isolate) { 658c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio return unicodeWrap(str, mDefaultTextDirectionHeuristicCompat, isolate); 65977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 66077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 66177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 66277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Operates like {@link #unicodeWrap(String, android.support.v4.text.TextDirectionHeuristicCompat, boolean)}, but uses the 66377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * formatter's default direction estimation algorithm and assumes {@code isolate} is true. 66477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 66577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str The input string. 66677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return Input string after applying the above processing. 66777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 66877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio public String unicodeWrap(String str) { 669c92d608279e5716a176e142abcd5e1b2148a0680Fabrice Di Meglio return unicodeWrap(str, mDefaultTextDirectionHeuristicCompat, true /* isolate */); 67077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 67177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 67277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 67377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Helper method to return true if the Locale directionality is RTL. 67477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 67577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param locale The Locale whose directionality will be checked to be RTL or LTR 67677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @return true if the {@code locale} directionality is RTL. False otherwise. 67777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 67877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static boolean isRtlLocale(Locale locale) { 67977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return (TextUtilsCompat.getLayoutDirectionFromLocale(locale) == ViewCompat.LAYOUT_DIRECTION_RTL); 68077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 68177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 68277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 68377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Enum for directionality type. 68477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 68577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final int DIR_LTR = -1; 68677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final int DIR_UNKNOWN = 0; 68777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final int DIR_RTL = +1; 68877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 68977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 69077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Returns the directionality of the last character with strong directionality in the string, or 69177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * DIR_UNKNOWN if none was encountered. For efficiency, actually scans backwards from the end of 69277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * the string. Treats a non-BN character between an LRE/RLE/LRO/RLO and its matching PDF as a 69377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * strong character, LTR after LRE/LRO, and RTL after RLE/RLO. The results are undefined for a 69477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * string containing unbalanced LRE/RLE/LRO/RLO/PDF characters. The intended use is to check 69577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * whether a logically separate item that starts with a number or a character of the string's 69677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * exit directionality and follows this string inline (not counting any neutral characters in 69777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * between) would "stick" to it in an opposite-directionality context, thus being displayed in 69877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * an incorrect position. An LRM or RLM character (the one of the context's directionality) 69977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * between the two will prevent such sticking. 70077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 70177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str the string to check. 70277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 70377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static int getExitDir(String str) { 70477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return new DirectionalityEstimator(str, false /* isHtml */).getExitDir(); 70577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 70677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 70777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 70877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Returns the directionality of the first character with strong directionality in the string, 70977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * or DIR_UNKNOWN if none was encountered. Treats a non-BN character between an 71077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * LRE/RLE/LRO/RLO and its matching PDF as a strong character, LTR after LRE/LRO, and RTL after 71177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * RLE/RLO. The results are undefined for a string containing unbalanced LRE/RLE/LRO/RLO/PDF 71277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * characters. The intended use is to check whether a logically separate item that ends with a 71377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * character of the string's entry directionality and precedes the string inline (not counting 71477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * any neutral characters in between) would "stick" to it in an opposite-directionality context, 71577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * thus being displayed in an incorrect position. An LRM or RLM character (the one of the 71677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * context's directionality) between the two will prevent such sticking. 71777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 71877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param str the string to check. 71977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 72077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static int getEntryDir(String str) { 72177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return new DirectionalityEstimator(str, false /* isHtml */).getEntryDir(); 72277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 72377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 72477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 72577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * An object that estimates the directionality of a given string by various methods. 72677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 72777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 72877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static class DirectionalityEstimator { 72977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 73077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // Internal static variables and constants. 73177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 73277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 73377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Size of the bidi character class cache. The results of the Character.getDirectionality() 73477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * calls on the lowest DIR_TYPE_CACHE_SIZE codepoints are kept in an array for speed. 73577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * The 0x700 value is designed to leave all the European and Near Eastern languages in the 73677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * cache. It can be reduced to 0x180, restricting the cache to the Western European 73777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * languages. 73877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 73977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final int DIR_TYPE_CACHE_SIZE = 0x700; 74077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 74177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 74277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * The bidi character class cache. 74377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 74477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static final byte DIR_TYPE_CACHE[]; 74577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 74677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio static { 74777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio DIR_TYPE_CACHE = new byte[DIR_TYPE_CACHE_SIZE]; 74877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio for (int i = 0; i < DIR_TYPE_CACHE_SIZE; i++) { 74977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio DIR_TYPE_CACHE[i] = Character.getDirectionality(i); 75077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 75177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 75277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 75377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // Internal instance variables. 75477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 75577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 75677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * The text to be scanned. 75777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 75877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private final String text; 75977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 76077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 76177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Whether the text to be scanned is to be treated as HTML, i.e. skipping over tags and 76277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * entities when looking for the next / preceding dir type. 76377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 76477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private final boolean isHtml; 76577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 76677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 76777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * The length of the text in chars. 76877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 76977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private final int length; 77077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 77177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 77277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * The current position in the text. 77377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 77477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private int charIndex; 77577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 77677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 77777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * The char encountered by the last dirTypeForward or dirTypeBackward call. If it 77877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * encountered a supplementary codepoint, this contains a char that is not a valid 77977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * codepoint. This is ok, because this member is only used to detect some well-known ASCII 78077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * syntax, e.g. "http://" and the beginning of an HTML tag or entity. 78177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 78277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private char lastChar; 78377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 78477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 78577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Constructor. 78677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 78777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param text The string to scan. 78877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @param isHtml Whether the text to be scanned is to be treated as HTML, i.e. skipping over 78977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * tags and entities. 79077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 79177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio DirectionalityEstimator(String text, boolean isHtml) { 79277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio this.text = text; 79377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio this.isHtml = isHtml; 79477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio length = text.length(); 79577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 79677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 79777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 79877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Returns the directionality of the first character with strong directionality in the 79977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * string, or DIR_UNKNOWN if none was encountered. Treats a non-BN character between an 80077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * LRE/RLE/LRO/RLO and its matching PDF as a strong character, LTR after LRE/LRO, and RTL 80177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * after RLE/RLO. The results are undefined for a string containing unbalanced 80277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * LRE/RLE/LRO/RLO/PDF characters. 80377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 80477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int getEntryDir() { 80577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // The reason for this method name, as opposed to getFirstStrongDir(), is that 80677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // "first strong" is a commonly used description of Unicode's estimation algorithm, 80777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // but the two must treat formatting characters quite differently. Thus, we are staying 80877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // away from both "first" and "last" in these method names to avoid confusion. 80977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio charIndex = 0; 81077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int embeddingLevel = 0; 81177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int embeddingLevelDir = DIR_UNKNOWN; 81277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int firstNonEmptyEmbeddingLevel = 0; 81377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio while (charIndex < length && firstNonEmptyEmbeddingLevel == 0) { 81477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio switch (dirTypeForward()) { 81577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING: 81677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE: 81777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio ++embeddingLevel; 81877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio embeddingLevelDir = DIR_LTR; 81977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 82077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING: 82177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE: 82277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio ++embeddingLevel; 82377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio embeddingLevelDir = DIR_RTL; 82477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 82577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT: 82677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio --embeddingLevel; 82777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // To restore embeddingLevelDir to its previous value, we would need a 82877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // stack, which we want to avoid. Thus, at this point we do not know the 82977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // current embedding's directionality. 83077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio embeddingLevelDir = DIR_UNKNOWN; 83177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 83277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_BOUNDARY_NEUTRAL: 83377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 83477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_LEFT_TO_RIGHT: 83577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (embeddingLevel == 0) { 83677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return DIR_LTR; 83777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 83877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio firstNonEmptyEmbeddingLevel = embeddingLevel; 83977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 84077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_RIGHT_TO_LEFT: 84177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC: 84277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (embeddingLevel == 0) { 84377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return DIR_RTL; 84477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 84577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio firstNonEmptyEmbeddingLevel = embeddingLevel; 84677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 84777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio default: 84877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio firstNonEmptyEmbeddingLevel = embeddingLevel; 84977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 85077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 85177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 85277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 85377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // We have either found a non-empty embedding or scanned the entire string finding 85477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // neither a non-empty embedding nor a strong character outside of an embedding. 85577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (firstNonEmptyEmbeddingLevel == 0) { 85677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // We have not found a non-empty embedding. Thus, the string contains neither a 85777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // non-empty embedding nor a strong character outside of an embedding. 85877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return DIR_UNKNOWN; 85977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 86077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 86177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // We have found a non-empty embedding. 86277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (embeddingLevelDir != DIR_UNKNOWN) { 86377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // We know the directionality of the non-empty embedding. 86477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return embeddingLevelDir; 86577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 86677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 86777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // We do not remember the directionality of the non-empty embedding we found. So, we go 86877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // backwards to find the start of the non-empty embedding and get its directionality. 86977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio while (charIndex > 0) { 87077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio switch (dirTypeBackward()) { 87177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING: 87277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE: 87377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (firstNonEmptyEmbeddingLevel == embeddingLevel) { 87477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return DIR_LTR; 87577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 87677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio --embeddingLevel; 87777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 87877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING: 87977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE: 88077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (firstNonEmptyEmbeddingLevel == embeddingLevel) { 88177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return DIR_RTL; 88277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 88377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio --embeddingLevel; 88477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 88577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT: 88677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio ++embeddingLevel; 88777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 88877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 88977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 89077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // We should never get here. 89177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return DIR_UNKNOWN; 89277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 89377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 89477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 89577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Returns the directionality of the last character with strong directionality in the 89677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * string, or DIR_UNKNOWN if none was encountered. For efficiency, actually scans backwards 89777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * from the end of the string. Treats a non-BN character between an LRE/RLE/LRO/RLO and its 89877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * matching PDF as a strong character, LTR after LRE/LRO, and RTL after RLE/RLO. The results 89977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * are undefined for a string containing unbalanced LRE/RLE/LRO/RLO/PDF characters. 90077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 90177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int getExitDir() { 90277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // The reason for this method name, as opposed to getLastStrongDir(), is that "last 90377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // strong" sounds like the exact opposite of "first strong", which is a commonly used 90477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // description of Unicode's estimation algorithm (getUnicodeDir() above), but the two 90577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // must treat formatting characters quite differently. Thus, we are staying away from 90677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // both "first" and "last" in these method names to avoid confusion. 90777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio charIndex = length; 90877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int embeddingLevel = 0; 90977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int lastNonEmptyEmbeddingLevel = 0; 91077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio while (charIndex > 0) { 91177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio switch (dirTypeBackward()) { 91277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_LEFT_TO_RIGHT: 91377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (embeddingLevel == 0) { 91477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return DIR_LTR; 91577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 91677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastNonEmptyEmbeddingLevel == 0) { 91777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio lastNonEmptyEmbeddingLevel = embeddingLevel; 91877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 91977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 92077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING: 92177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE: 92277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastNonEmptyEmbeddingLevel == embeddingLevel) { 92377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return DIR_LTR; 92477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 92577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio --embeddingLevel; 92677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 92777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_RIGHT_TO_LEFT: 92877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC: 92977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (embeddingLevel == 0) { 93077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return DIR_RTL; 93177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 93277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastNonEmptyEmbeddingLevel == 0) { 93377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio lastNonEmptyEmbeddingLevel = embeddingLevel; 93477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 93577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 93677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING: 93777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE: 93877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastNonEmptyEmbeddingLevel == embeddingLevel) { 93977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return DIR_RTL; 94077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 94177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio --embeddingLevel; 94277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 94377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT: 94477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio ++embeddingLevel; 94577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 94677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio case Character.DIRECTIONALITY_BOUNDARY_NEUTRAL: 94777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 94877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio default: 94977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastNonEmptyEmbeddingLevel == 0) { 95077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio lastNonEmptyEmbeddingLevel = embeddingLevel; 95177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 95277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 95377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 95477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 95577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return DIR_UNKNOWN; 95677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 95777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 95877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // Internal methods 95977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 96077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 96177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Gets the bidi character class, i.e. Character.getDirectionality(), of a given char, using 96277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * a cache for speed. Not designed for supplementary codepoints, whose results we do not 96377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * cache. 96477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 96577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private static byte getCachedDirectionality(char c) { 96677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return c < DIR_TYPE_CACHE_SIZE ? DIR_TYPE_CACHE[c] : Character.getDirectionality(c); 96777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 96877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 96977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 97077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Returns the Character.DIRECTIONALITY_... value of the next codepoint and advances 97177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * charIndex. If isHtml, and the codepoint is '<' or '&', advances through the tag/entity, 97277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * and returns Character.DIRECTIONALITY_WHITESPACE. For an entity, it would be best to 97377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * figure out the actual character, and return its dirtype, but treating it as whitespace is 97477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * good enough for our purposes. 97577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 97677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @throws java.lang.IndexOutOfBoundsException if called when charIndex >= length or < 0. 97777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 97877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio byte dirTypeForward() { 97977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio lastChar = text.charAt(charIndex); 98077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (Character.isHighSurrogate(lastChar)) { 98177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int codePoint = Character.codePointAt(text, charIndex); 98277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio charIndex += Character.charCount(codePoint); 98377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return Character.getDirectionality(codePoint); 98477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 98577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio charIndex++; 98677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio byte dirType = getCachedDirectionality(lastChar); 98777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (isHtml) { 98877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // Process tags and entities. 98977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastChar == '<') { 99077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio dirType = skipTagForward(); 99177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } else if (lastChar == '&') { 99277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio dirType = skipEntityForward(); 99377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 99477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 99577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return dirType; 99677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 99777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 99877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 99977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Returns the Character.DIRECTIONALITY_... value of the preceding codepoint and advances 100077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * charIndex backwards. If isHtml, and the codepoint is the end of a complete HTML tag or 100177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * entity, advances over the whole tag/entity and returns 100277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Character.DIRECTIONALITY_WHITESPACE. For an entity, it would be best to figure out the 100377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * actual character, and return its dirtype, but treating it as whitespace is good enough 100477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * for our purposes. 100577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * 100677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * @throws java.lang.IndexOutOfBoundsException if called when charIndex > length or <= 0. 100777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 100877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio byte dirTypeBackward() { 100977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio lastChar = text.charAt(charIndex - 1); 101077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (Character.isLowSurrogate(lastChar)) { 101177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int codePoint = Character.codePointBefore(text, charIndex); 101277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio charIndex -= Character.charCount(codePoint); 101377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return Character.getDirectionality(codePoint); 101477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 101577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio charIndex--; 101677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio byte dirType = getCachedDirectionality(lastChar); 101777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (isHtml) { 101877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // Process tags and entities. 101977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastChar == '>') { 102077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio dirType = skipTagBackward(); 102177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } else if (lastChar == ';') { 102277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio dirType = skipEntityBackward(); 102377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 102477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 102577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return dirType; 102677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 102777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 102877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 102977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Advances charIndex forward through an HTML tag (after the opening < has already been 103077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * read) and returns Character.DIRECTIONALITY_WHITESPACE. If there is no matching >, 103177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * does not change charIndex and returns Character.DIRECTIONALITY_OTHER_NEUTRALS (for the 103277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * < that hadn't been part of a tag after all). 103377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 103477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private byte skipTagForward() { 103577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int initialCharIndex = charIndex; 103677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio while (charIndex < length) { 103777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio lastChar = text.charAt(charIndex++); 103877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastChar == '>') { 103977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // The end of the tag. 104077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return Character.DIRECTIONALITY_WHITESPACE; 104177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 104277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastChar == '"' || lastChar == '\'') { 104377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // Skip over a quoted attribute value inside the tag. 104477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio char quote = lastChar; 104577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio while (charIndex < length && (lastChar = text.charAt(charIndex++)) != quote) {} 104677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 104777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 104877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // The original '<' wasn't the start of a tag after all. 104977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio charIndex = initialCharIndex; 105077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio lastChar = '<'; 105177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return Character.DIRECTIONALITY_OTHER_NEUTRALS; 105277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 105377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 105477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 105577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Advances charIndex backward through an HTML tag (after the closing > has already been 105677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * read) and returns Character.DIRECTIONALITY_WHITESPACE. If there is no matching <, does 105777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * not change charIndex and returns Character.DIRECTIONALITY_OTHER_NEUTRALS (for the > 105877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * that hadn't been part of a tag after all). Nevertheless, the running time for calling 105977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * skipTagBackward() in a loop remains linear in the size of the text, even for a text like 106077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * ">>>>", because skipTagBackward() also stops looking for a matching < 106177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * when it encounters another >. 106277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 106377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private byte skipTagBackward() { 106477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int initialCharIndex = charIndex; 106577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio while (charIndex > 0) { 106677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio lastChar = text.charAt(--charIndex); 106777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastChar == '<') { 106877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // The start of the tag. 106977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return Character.DIRECTIONALITY_WHITESPACE; 107077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 107177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastChar == '>') { 107277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 107377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 107477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastChar == '"' || lastChar == '\'') { 107577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // Skip over a quoted attribute value inside the tag. 107677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio char quote = lastChar; 107777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio while (charIndex > 0 && (lastChar = text.charAt(--charIndex)) != quote) {} 107877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 107977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 108077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio // The original '>' wasn't the end of a tag after all. 108177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio charIndex = initialCharIndex; 108277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio lastChar = '>'; 108377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return Character.DIRECTIONALITY_OTHER_NEUTRALS; 108477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 108577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 108677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 108777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Advances charIndex forward through an HTML character entity tag (after the opening 108877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * & has already been read) and returns Character.DIRECTIONALITY_WHITESPACE. It would be 108977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * best to figure out the actual character and return its dirtype, but this is good enough. 109077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 109177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private byte skipEntityForward() { 109277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio while (charIndex < length && (lastChar = text.charAt(charIndex++)) != ';') {} 109377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return Character.DIRECTIONALITY_WHITESPACE; 109477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 109577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio 109677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio /** 109777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Advances charIndex backward through an HTML character entity tag (after the closing ; 109877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * has already been read) and returns Character.DIRECTIONALITY_WHITESPACE. It would be best 109977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * to figure out the actual character and return its dirtype, but this is good enough. 110077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * If there is no matching &, does not change charIndex and returns 110177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * Character.DIRECTIONALITY_OTHER_NEUTRALS (for the ';' that did not start an entity after 110277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * all). Nevertheless, the running time for calling skipEntityBackward() in a loop remains 110377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * linear in the size of the text, even for a text like ";;;;;;;", because skipTagBackward() 110477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio * also stops looking for a matching & when it encounters another ;. 110577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio */ 110677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio private byte skipEntityBackward() { 110777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio int initialCharIndex = charIndex; 110877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio while (charIndex > 0) { 110977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio lastChar = text.charAt(--charIndex); 111077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastChar == '&') { 111177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return Character.DIRECTIONALITY_WHITESPACE; 111277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 111377f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio if (lastChar == ';') { 111477f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio break; 111577f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 111677f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 111777f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio charIndex = initialCharIndex; 111877f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio lastChar = ';'; 111977f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio return Character.DIRECTIONALITY_OTHER_NEUTRALS; 112077f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 112177f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio } 112277f6bada6f88acea9025afce3eb0127d45411798Fabrice Di Meglio}