|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
/* |
|
******************************************************************************* |
|
* Copyright (C) 2001-2014, International Business Machines |
|
* Corporation and others. All Rights Reserved. |
|
******************************************************************************* |
|
*/ |
|
|
|
/* FOOD FOR THOUGHT: currently the reordering modes are a mixture of |
|
* algorithm for direct BiDi, algorithm for inverse Bidi and the bizarre |
|
* concept of RUNS_ONLY which is a double operation. |
|
* It could be advantageous to divide this into 3 concepts: |
|
* a) Operation: direct / inverse / RUNS_ONLY |
|
* b) Direct algorithm: default / NUMBERS_SPECIAL / GROUP_NUMBERS_WITH_L |
|
* c) Inverse algorithm: default / INVERSE_LIKE_DIRECT / NUMBERS_SPECIAL |
|
* This would allow combinations not possible today like RUNS_ONLY with |
|
* NUMBERS_SPECIAL. |
|
* Also allow to set INSERT_MARKS for the direct step of RUNS_ONLY and |
|
* REMOVE_CONTROLS for the inverse step. |
|
* Not all combinations would be supported, and probably not all do make sense. |
|
* This would need to document which ones are supported and what are the |
|
* fallbacks for unsupported combinations. |
|
*/ |
|
|
|
package sun.text.bidi; |
|
|
|
import java.lang.reflect.Array; |
|
import java.text.AttributedCharacterIterator; |
|
import java.text.Bidi; |
|
import java.util.Arrays; |
|
import jdk.internal.misc.JavaAWTFontAccess; |
|
import jdk.internal.misc.SharedSecrets; |
|
import sun.text.normalizer.UBiDiProps; |
|
import sun.text.normalizer.UCharacter; |
|
import sun.text.normalizer.UTF16; |
|
|
|
/** |
|
* |
|
* <h2>Bidi algorithm for ICU</h2> |
|
* |
|
* This is an implementation of the Unicode Bidirectional Algorithm. The |
|
* algorithm is defined in the <a |
|
* href="http://www.unicode.org/unicode/reports/tr9/">Unicode Standard Annex #9</a>. |
|
* <p> |
|
* |
|
* Note: Libraries that perform a bidirectional algorithm and reorder strings |
|
* accordingly are sometimes called "Storage Layout Engines". ICU's Bidi and |
|
* shaping (ArabicShaping) classes can be used at the core of such "Storage |
|
* Layout Engines". |
|
* |
|
* <h3>General remarks about the API:</h3> |
|
* |
|
* The "limit" of a sequence of characters is the position just after |
|
* their last character, i.e., one more than that position. |
|
* <p> |
|
* |
|
* Some of the API methods provide access to "runs". Such a |
|
* "run" is defined as a sequence of characters that are at the same |
|
* embedding level after performing the Bidi algorithm. |
|
* |
|
* <h3>Basic concept: paragraph</h3> |
|
* A piece of text can be divided into several paragraphs by characters |
|
* with the Bidi class <code>Block Separator</code>. For handling of |
|
* paragraphs, see: |
|
* <ul> |
|
* <li>{@link #countParagraphs} |
|
* <li>{@link #getParaLevel} |
|
* <li>{@link #getParagraph} |
|
* <li>{@link #getParagraphByIndex} |
|
* </ul> |
|
* |
|
* <h3>Basic concept: text direction</h3> |
|
* The direction of a piece of text may be: |
|
* <ul> |
|
* <li>{@link #LTR} |
|
* <li>{@link #RTL} |
|
* <li>{@link #MIXED} |
|
* <li>{@link #NEUTRAL} |
|
* </ul> |
|
* |
|
* <h3>Basic concept: levels</h3> |
|
* |
|
* Levels in this API represent embedding levels according to the Unicode |
|
* Bidirectional Algorithm. |
|
* Their low-order bit (even/odd value) indicates the visual direction.<p> |
|
* |
|
* Levels can be abstract values when used for the |
|
* <code>paraLevel</code> and <code>embeddingLevels</code> |
|
* arguments of <code>setPara()</code>; there: |
|
* <ul> |
|
* <li>the high-order bit of an <code>embeddingLevels[]</code> |
|
* value indicates whether the using application is |
|
* specifying the level of a character to <i>override</i> whatever the |
|
* Bidi implementation would resolve it to.</li> |
|
* <li><code>paraLevel</code> can be set to the |
|
* pseudo-level values <code>LEVEL_DEFAULT_LTR</code> |
|
* and <code>LEVEL_DEFAULT_RTL</code>.</li> |
|
* </ul> |
|
* |
|
* <p>The related constants are not real, valid level values. |
|
* <code>DEFAULT_XXX</code> can be used to specify |
|
* a default for the paragraph level for |
|
* when the <code>setPara()</code> method |
|
* shall determine it but there is no |
|
* strongly typed character in the input.<p> |
|
* |
|
* Note that the value for <code>LEVEL_DEFAULT_LTR</code> is even |
|
* and the one for <code>LEVEL_DEFAULT_RTL</code> is odd, |
|
* just like with normal LTR and RTL level values - |
|
* these special values are designed that way. Also, the implementation |
|
* assumes that MAX_EXPLICIT_LEVEL is odd. |
|
* |
|
* <p><b>See Also:</b> |
|
* <ul> |
|
* <li>{@link #LEVEL_DEFAULT_LTR} |
|
* <li>{@link #LEVEL_DEFAULT_RTL} |
|
* <li>{@link #LEVEL_OVERRIDE} |
|
* <li>{@link #MAX_EXPLICIT_LEVEL} |
|
* <li>{@link #setPara} |
|
* </ul> |
|
* |
|
* <h3>Basic concept: Reordering Mode</h3> |
|
* Reordering mode values indicate which variant of the Bidi algorithm to |
|
* use. |
|
* |
|
* <p><b>See Also:</b> |
|
* <ul> |
|
* <li>{@link #setReorderingMode} |
|
* <li>{@link #REORDER_DEFAULT} |
|
* <li>{@link #REORDER_NUMBERS_SPECIAL} |
|
* <li>{@link #REORDER_GROUP_NUMBERS_WITH_R} |
|
* <li>{@link #REORDER_RUNS_ONLY} |
|
* <li>{@link #REORDER_INVERSE_NUMBERS_AS_L} |
|
* <li>{@link #REORDER_INVERSE_LIKE_DIRECT} |
|
* <li>{@link #REORDER_INVERSE_FOR_NUMBERS_SPECIAL} |
|
* </ul> |
|
* |
|
* <h3>Basic concept: Reordering Options</h3> |
|
* Reordering options can be applied during Bidi text transformations. |
|
* |
|
* <p><b>See Also:</b> |
|
* <ul> |
|
* <li>{@link #setReorderingOptions} |
|
* <li>{@link #OPTION_DEFAULT} |
|
* <li>{@link #OPTION_INSERT_MARKS} |
|
* <li>{@link #OPTION_REMOVE_CONTROLS} |
|
* <li>{@link #OPTION_STREAMING} |
|
* </ul> |
|
* |
|
* |
|
* @author Simon Montagu, Matitiahu Allouche (ported from C code written by Markus W. Scherer) |
|
* @stable ICU 3.8 |
|
* |
|
* |
|
* <h4> Sample code for the ICU Bidi API </h4> |
|
* |
|
* <h5>Rendering a paragraph with the ICU Bidi API</h5> |
|
* |
|
* This is (hypothetical) sample code that illustrates how the ICU Bidi API |
|
* could be used to render a paragraph of text. Rendering code depends highly on |
|
* the graphics system, therefore this sample code must make a lot of |
|
* assumptions, which may or may not match any existing graphics system's |
|
* properties. |
|
* |
|
* <p> |
|
* The basic assumptions are: |
|
* </p> |
|
* <ul> |
|
* <li>Rendering is done from left to right on a horizontal line.</li> |
|
* <li>A run of single-style, unidirectional text can be rendered at once. |
|
* </li> |
|
* <li>Such a run of text is passed to the graphics system with characters |
|
* (code units) in logical order.</li> |
|
* <li>The line-breaking algorithm is very complicated and Locale-dependent - |
|
* and therefore its implementation omitted from this sample code.</li> |
|
* </ul> |
|
* |
|
* <pre>{@code |
|
* |
|
* package com.ibm.icu.dev.test.bidi; |
|
* |
|
* import com.ibm.icu.text.Bidi; |
|
* import com.ibm.icu.text.BidiRun; |
|
* |
|
* public class Sample { |
|
* |
|
* static final int styleNormal = 0; |
|
* static final int styleSelected = 1; |
|
* static final int styleBold = 2; |
|
* static final int styleItalics = 4; |
|
* static final int styleSuper=8; |
|
* static final int styleSub = 16; |
|
* |
|
* static class StyleRun { |
|
* int limit; |
|
* int style; |
|
* |
|
* public StyleRun(int limit, int style) { |
|
* this.limit = limit; |
|
* this.style = style; |
|
* } |
|
* } |
|
* |
|
* static class Bounds { |
|
* int start; |
|
* int limit; |
|
* |
|
* public Bounds(int start, int limit) { |
|
* this.start = start; |
|
* this.limit = limit; |
|
* } |
|
* } |
|
* |
|
* static int getTextWidth(String text, int start, int limit, |
|
* StyleRun[] styleRuns, int styleRunCount) { |
|
* // simplistic way to compute the width |
|
* return limit - start; |
|
* } |
|
* |
|
* // set limit and StyleRun limit for a line |
|
* // from text[start] and from styleRuns[styleRunStart] |
|
* // using Bidi.getLogicalRun(...) |
|
* // returns line width |
|
* static int getLineBreak(String text, Bounds line, Bidi para, |
|
* StyleRun styleRuns[], Bounds styleRun) { |
|
* // dummy return |
|
* return 0; |
|
* } |
|
* |
|
* // render runs on a line sequentially, always from left to right |
|
* |
|
* // prepare rendering a new line |
|
* static void startLine(byte textDirection, int lineWidth) { |
|
* System.out.println(); |
|
* } |
|
* |
|
* // render a run of text and advance to the right by the run width |
|
* // the text[start..limit-1] is always in logical order |
|
* static void renderRun(String text, int start, int limit, |
|
* byte textDirection, int style) { |
|
* } |
|
* |
|
* // We could compute a cross-product |
|
* // from the style runs with the directional runs |
|
* // and then reorder it. |
|
* // Instead, here we iterate over each run type |
|
* // and render the intersections - |
|
* // with shortcuts in simple (and common) cases. |
|
* // renderParagraph() is the main function. |
|
* |
|
* // render a directional run with |
|
* // (possibly) multiple style runs intersecting with it |
|
* static void renderDirectionalRun(String text, int start, int limit, |
|
* byte direction, StyleRun styleRuns[], |
|
* int styleRunCount) { |
|
* int i; |
|
* |
|
* // iterate over style runs |
|
* if (direction == Bidi.LTR) { |
|
* int styleLimit; |
|
* for (i = 0; i < styleRunCount; ++i) { |
|
* styleLimit = styleRuns[i].limit; |
|
* if (start < styleLimit) { |
|
* if (styleLimit > limit) { |
|
* styleLimit = limit; |
|
* } |
|
* renderRun(text, start, styleLimit, |
|
* direction, styleRuns[i].style); |
|
* if (styleLimit == limit) { |
|
* break; |
|
* } |
|
* start = styleLimit; |
|
* } |
|
* } |
|
* } else { |
|
* int styleStart; |
|
* |
|
* for (i = styleRunCount-1; i >= 0; --i) { |
|
* if (i > 0) { |
|
* styleStart = styleRuns[i-1].limit; |
|
* } else { |
|
* styleStart = 0; |
|
* } |
|
* if (limit >= styleStart) { |
|
* if (styleStart < start) { |
|
* styleStart = start; |
|
* } |
|
* renderRun(text, styleStart, limit, direction, |
|
* styleRuns[i].style); |
|
* if (styleStart == start) { |
|
* break; |
|
* } |
|
* limit = styleStart; |
|
* } |
|
* } |
|
* } |
|
* } |
|
* |
|
* // the line object represents text[start..limit-1] |
|
* static void renderLine(Bidi line, String text, int start, int limit, |
|
* StyleRun styleRuns[], int styleRunCount) { |
|
* byte direction = line.getDirection(); |
|
* if (direction != Bidi.MIXED) { |
|
* // unidirectional |
|
* if (styleRunCount <= 1) { |
|
* renderRun(text, start, limit, direction, styleRuns[0].style); |
|
* } else { |
|
* renderDirectionalRun(text, start, limit, direction, |
|
* styleRuns, styleRunCount); |
|
* } |
|
* } else { |
|
* // mixed-directional |
|
* int count, i; |
|
* BidiRun run; |
|
* |
|
* try { |
|
* count = line.countRuns(); |
|
* } catch (IllegalStateException e) { |
|
* e.printStackTrace(); |
|
* return; |
|
* } |
|
* if (styleRunCount <= 1) { |
|
* int style = styleRuns[0].style; |
|
* |
|
* // iterate over directional runs |
|
* for (i = 0; i < count; ++i) { |
|
* run = line.getVisualRun(i); |
|
* renderRun(text, run.getStart(), run.getLimit(), |
|
* run.getDirection(), style); |
|
* } |
|
* } else { |
|
* // iterate over both directional and style runs |
|
* for (i = 0; i < count; ++i) { |
|
* run = line.getVisualRun(i); |
|
* renderDirectionalRun(text, run.getStart(), |
|
* run.getLimit(), run.getDirection(), |
|
* styleRuns, styleRunCount); |
|
* } |
|
* } |
|
* } |
|
* } |
|
* |
|
* static void renderParagraph(String text, byte textDirection, |
|
* StyleRun styleRuns[], int styleRunCount, |
|
* int lineWidth) { |
|
* int length = text.length(); |
|
* Bidi para = new Bidi(); |
|
* try { |
|
* para.setPara(text, |
|
* textDirection != 0 ? Bidi.LEVEL_DEFAULT_RTL |
|
* : Bidi.LEVEL_DEFAULT_LTR, |
|
* null); |
|
* } catch (Exception e) { |
|
* e.printStackTrace(); |
|
* return; |
|
* } |
|
* byte paraLevel = (byte)(1 & para.getParaLevel()); |
|
* StyleRun styleRun = new StyleRun(length, styleNormal); |
|
* |
|
* if (styleRuns == null || styleRunCount <= 0) { |
|
* styleRuns = new StyleRun[1]; |
|
* styleRunCount = 1; |
|
* styleRuns[0] = styleRun; |
|
* } |
|
* // assume styleRuns[styleRunCount-1].limit>=length |
|
* |
|
* int width = getTextWidth(text, 0, length, styleRuns, styleRunCount); |
|
* if (width <= lineWidth) { |
|
* // everything fits onto one line |
|
* |
|
* // prepare rendering a new line from either left or right |
|
* startLine(paraLevel, width); |
|
* |
|
* renderLine(para, text, 0, length, styleRuns, styleRunCount); |
|
* } else { |
|
* // we need to render several lines |
|
* Bidi line = new Bidi(length, 0); |
|
* int start = 0, limit; |
|
* int styleRunStart = 0, styleRunLimit; |
|
* |
|
* for (;;) { |
|
* limit = length; |
|
* styleRunLimit = styleRunCount; |
|
* width = getLineBreak(text, new Bounds(start, limit), |
|
* para, styleRuns, |
|
* new Bounds(styleRunStart, styleRunLimit)); |
|
* try { |
|
* line = para.setLine(start, limit); |
|
* } catch (Exception e) { |
|
* e.printStackTrace(); |
|
* return; |
|
* } |
|
* // prepare rendering a new line |
|
* // from either left or right |
|
* startLine(paraLevel, width); |
|
* |
|
* if (styleRunStart > 0) { |
|
* int newRunCount = styleRuns.length - styleRunStart; |
|
* StyleRun[] newRuns = new StyleRun[newRunCount]; |
|
* System.arraycopy(styleRuns, styleRunStart, newRuns, 0, |
|
* newRunCount); |
|
* renderLine(line, text, start, limit, newRuns, |
|
* styleRunLimit - styleRunStart); |
|
* } else { |
|
* renderLine(line, text, start, limit, styleRuns, |
|
* styleRunLimit - styleRunStart); |
|
* } |
|
* if (limit == length) { |
|
* break; |
|
* } |
|
* start = limit; |
|
* styleRunStart = styleRunLimit - 1; |
|
* if (start >= styleRuns[styleRunStart].limit) { |
|
* ++styleRunStart; |
|
* } |
|
* } |
|
* } |
|
* } |
|
* |
|
* public static void main(String[] args) |
|
* { |
|
* renderParagraph("Some Latin text...", Bidi.LTR, null, 0, 80); |
|
* renderParagraph("Some Hebrew text...", Bidi.RTL, null, 0, 60); |
|
* } |
|
* } |
|
* |
|
* }</pre> |
|
*/ |
|
|
|
/* |
|
* General implementation notes: |
|
* |
|
* Throughout the implementation, there are comments like (W2) that refer to |
|
* rules of the BiDi algorithm, in this example to the second rule of the |
|
* resolution of weak types. |
|
* |
|
* For handling surrogate pairs, where two UChar's form one "abstract" (or UTF-32) |
|
* character according to UTF-16, the second UChar gets the directional property of |
|
* the entire character assigned, while the first one gets a BN, a boundary |
|
* neutral, type, which is ignored by most of the algorithm according to |
|
* rule (X9) and the implementation suggestions of the BiDi algorithm. |
|
* |
|
* Later, adjustWSLevels() will set the level for each BN to that of the |
|
* following character (UChar), which results in surrogate pairs getting the |
|
* same level on each of their surrogates. |
|
* |
|
* In a UTF-8 implementation, the same thing could be done: the last byte of |
|
* a multi-byte sequence would get the "real" property, while all previous |
|
* bytes of that sequence would get BN. |
|
* |
|
* It is not possible to assign all those parts of a character the same real |
|
* property because this would fail in the resolution of weak types with rules |
|
* that look at immediately surrounding types. |
|
* |
|
* As a related topic, this implementation does not remove Boundary Neutral |
|
* types from the input, but ignores them wherever this is relevant. |
|
* For example, the loop for the resolution of the weak types reads |
|
* types until it finds a non-BN. |
|
* Also, explicit embedding codes are neither changed into BN nor removed. |
|
* They are only treated the same way real BNs are. |
|
* As stated before, adjustWSLevels() takes care of them at the end. |
|
* For the purpose of conformance, the levels of all these codes |
|
* do not matter. |
|
* |
|
* Note that this implementation modifies the dirProps |
|
* after the initial setup, when applying X5c (replace FSI by LRI or RLI), |
|
* X6, N0 (replace paired brackets by L or R). |
|
* |
|
* In this implementation, the resolution of weak types (W1 to W6), |
|
* neutrals (N1 and N2), and the assignment of the resolved level (In) |
|
* are all done in one single loop, in resolveImplicitLevels(). |
|
* Changes of dirProp values are done on the fly, without writing |
|
* them back to the dirProps array. |
|
* |
|
* |
|
* This implementation contains code that allows to bypass steps of the |
|
* algorithm that are not needed on the specific paragraph |
|
* in order to speed up the most common cases considerably, |
|
* like text that is entirely LTR, or RTL text without numbers. |
|
* |
|
* Most of this is done by setting a bit for each directional property |
|
* in a flags variable and later checking for whether there are |
|
* any LTR characters or any RTL characters, or both, whether |
|
* there are any explicit embedding codes, etc. |
|
* |
|
* If the (Xn) steps are performed, then the flags are re-evaluated, |
|
* because they will then not contain the embedding codes any more |
|
* and will be adjusted for override codes, so that subsequently |
|
* more bypassing may be possible than what the initial flags suggested. |
|
* |
|
* If the text is not mixed-directional, then the |
|
* algorithm steps for the weak type resolution are not performed, |
|
* and all levels are set to the paragraph level. |
|
* |
|
* If there are no explicit embedding codes, then the (Xn) steps |
|
* are not performed. |
|
* |
|
* If embedding levels are supplied as a parameter, then all |
|
* explicit embedding codes are ignored, and the (Xn) steps |
|
* are not performed. |
|
* |
|
* White Space types could get the level of the run they belong to, |
|
* and are checked with a test of (flags&MASK_EMBEDDING) to |
|
* consider if the paragraph direction should be considered in |
|
* the flags variable. |
|
* |
|
* If there are no White Space types in the paragraph, then |
|
* (L1) is not necessary in adjustWSLevels(). |
|
*/ |
|
|
|
public class BidiBase { |
|
|
|
static class Point { |
|
int pos; |
|
int flag; /* flag for LRM/RLM, before/after */ |
|
} |
|
|
|
static class InsertPoints { |
|
int size; |
|
int confirmed; |
|
Point[] points = new Point[0]; |
|
} |
|
|
|
static class Opening { |
|
int position; |
|
int match; |
|
int contextPos; |
|
short flags; |
|
byte contextDir; /* L or R according to last strong char before opening */ |
|
} |
|
|
|
static class IsoRun { |
|
int contextPos; |
|
short start; |
|
short limit; |
|
byte level; |
|
byte lastStrong; |
|
byte lastBase; |
|
byte contextDir; /* L or R to use as context for following openings */ |
|
} |
|
|
|
static class BracketData { |
|
Opening[] openings = new Opening[SIMPLE_PARAS_COUNT]; |
|
int isoRunLast; /* index of last used entry */ |
|
|
|
+ 1 for index 0, + 1 for before the first isolated sequence */ |
|
IsoRun[] isoRuns = new IsoRun[MAX_EXPLICIT_LEVEL+2]; |
|
boolean isNumbersSpecial; /*reordering mode for NUMBERS_SPECIAL */ |
|
} |
|
|
|
static class Isolate { |
|
int startON; |
|
int start1; |
|
short stateImp; |
|
short state; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static final byte LEVEL_DEFAULT_LTR = (byte)0x7e; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static final byte LEVEL_DEFAULT_RTL = (byte)0x7f; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static final byte MAX_EXPLICIT_LEVEL = 125; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static final byte LEVEL_OVERRIDE = (byte)0x80; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static final int MAP_NOWHERE = -1; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static final byte LTR = 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static final byte RTL = 1; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static final byte MIXED = 2; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static final short KEEP_BASE_COMBINING = 1; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static final short DO_MIRRORING = 2; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static final short INSERT_LRM_FOR_NUMERIC = 4; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static final short REMOVE_BIDI_CONTROLS = 8; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static final short OUTPUT_REVERSE = 16; |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final short REORDER_DEFAULT = 0; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static final short REORDER_NUMBERS_SPECIAL = 1; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static final short REORDER_GROUP_NUMBERS_WITH_R = 2; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static final short REORDER_RUNS_ONLY = 3; |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static final short REORDER_INVERSE_NUMBERS_AS_L = 4; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static final short REORDER_INVERSE_LIKE_DIRECT = 5; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static final short REORDER_INVERSE_FOR_NUMBERS_SPECIAL = 6; |
|
|
|
|
|
|
|
*/ |
|
private static final short REORDER_LAST_LOGICAL_TO_VISUAL = |
|
REORDER_NUMBERS_SPECIAL; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static final int OPTION_INSERT_MARKS = 1; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static final int OPTION_REMOVE_CONTROLS = 2; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static final int OPTION_STREAMING = 4; |
|
|
|
/* |
|
* Comparing the description of the Bidi algorithm with this implementation |
|
* is easier with the same names for the Bidi types in the code as there. |
|
* See UCharacterDirection |
|
*/ |
|
static final byte L = 0; |
|
private static final byte R = 1; |
|
private static final byte EN = 2; |
|
private static final byte ES = 3; |
|
private static final byte ET = 4; |
|
private static final byte AN = 5; |
|
private static final byte CS = 6; |
|
static final byte B = 7; |
|
private static final byte S = 8; |
|
private static final byte WS = 9; |
|
private static final byte ON = 10; |
|
private static final byte LRE = 11; |
|
private static final byte LRO = 12; |
|
private static final byte AL = 13; |
|
private static final byte RLE = 14; |
|
private static final byte RLO = 15; |
|
private static final byte PDF = 16; |
|
private static final byte NSM = 17; |
|
private static final byte BN = 18; |
|
private static final byte FSI = 19; |
|
private static final byte LRI = 20; |
|
private static final byte RLI = 21; |
|
private static final byte PDI = 22; |
|
private static final byte ENL = PDI + 1; |
|
private static final byte ENR = ENL + 1; /* EN not subject to W7 */ |
|
|
|
|
|
private static final int CHAR_DIRECTION_COUNT = 23; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static final int BIDI_PAIRED_BRACKET_TYPE = 0x1015; |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static interface BidiPairedBracketType { |
|
|
|
|
|
|
|
*/ |
|
public static final int NONE = 0; |
|
|
|
|
|
|
|
*/ |
|
public static final int OPEN = 1; |
|
|
|
|
|
|
|
*/ |
|
public static final int CLOSE = 2; |
|
|
|
|
|
*/ |
|
public static final int COUNT = 3; |
|
} |
|
|
|
|
|
static final int SIMPLE_PARAS_COUNT = 10; |
|
|
|
private static final char CR = '\r'; |
|
private static final char LF = '\n'; |
|
|
|
static final int LRM_BEFORE = 1; |
|
static final int LRM_AFTER = 2; |
|
static final int RLM_BEFORE = 4; |
|
static final int RLM_AFTER = 8; |
|
|
|
|
|
static final byte FOUND_L = (byte)DirPropFlag(L); |
|
static final byte FOUND_R = (byte)DirPropFlag(R); |
|
|
|
|
|
|
|
|
|
*/ |
|
static final int ISOLATE = 0x0100; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
BidiBase paraBidi; |
|
|
|
final UBiDiProps bdp; |
|
|
|
|
|
char[] text; |
|
|
|
|
|
int originalLength; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public int length; |
|
|
|
|
|
|
|
|
|
*/ |
|
int resultLength; |
|
|
|
|
|
boolean mayAllocateText; |
|
boolean mayAllocateRuns; |
|
|
|
|
|
byte[] dirPropsMemory = new byte[1]; |
|
byte[] levelsMemory = new byte[1]; |
|
byte[] dirProps; |
|
byte[] levels; |
|
|
|
|
|
boolean isInverse; |
|
|
|
|
|
int reorderingMode; |
|
|
|
|
|
int reorderingOptions; |
|
|
|
|
|
boolean orderParagraphsLTR; |
|
|
|
|
|
byte paraLevel; |
|
|
|
/* original paraLevel when contextual */ |
|
|
|
byte defaultParaLevel; |
|
|
|
/* the following is set in setPara, used in processPropertySeq */ |
|
|
|
ImpTabPair impTabPair; /* reference to levels state table pair */ |
|
|
|
|
|
byte direction; |
|
|
|
|
|
int flags; |
|
|
|
|
|
int lastArabicPos; |
|
|
|
/* characters after trailingWSStart are WS and are */ |
|
|
|
int trailingWSStart; |
|
|
|
|
|
int paraCount; |
|
int[] paras_limit = new int[SIMPLE_PARAS_COUNT]; |
|
byte[] paras_level = new byte[SIMPLE_PARAS_COUNT]; |
|
|
|
|
|
int runCount; |
|
BidiRun[] runsMemory = new BidiRun[0]; |
|
BidiRun[] runs; |
|
|
|
|
|
BidiRun[] simpleRuns = {new BidiRun()}; |
|
|
|
|
|
Isolate[] isolates; |
|
|
|
/* maximum or current nesting depth of isolate sequences */ |
|
|
|
|
|
|
|
stack entry. */ |
|
int isolateCount; |
|
|
|
|
|
int[] logicalToVisualRunsMap; |
|
|
|
boolean isGoodLogicalToVisualRunsMap; |
|
|
|
|
|
InsertPoints insertPoints = new InsertPoints(); |
|
|
|
|
|
int controlCount; |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static int DirPropFlag(byte dir) { |
|
return (1 << dir); |
|
} |
|
|
|
boolean testDirPropFlagAt(int flag, int index) { |
|
return ((DirPropFlag(dirProps[index]) & flag) != 0); |
|
} |
|
|
|
static final int DirPropFlagMultiRuns = DirPropFlag((byte)31); |
|
|
|
|
|
static final int DirPropFlagLR[] = { DirPropFlag(L), DirPropFlag(R) }; |
|
static final int DirPropFlagE[] = { DirPropFlag(LRE), DirPropFlag(RLE) }; |
|
static final int DirPropFlagO[] = { DirPropFlag(LRO), DirPropFlag(RLO) }; |
|
|
|
static final int DirPropFlagLR(byte level) { return DirPropFlagLR[level & 1]; } |
|
static final int DirPropFlagE(byte level) { return DirPropFlagE[level & 1]; } |
|
static final int DirPropFlagO(byte level) { return DirPropFlagO[level & 1]; } |
|
static final byte DirFromStrong(byte strong) { return strong == L ? L : R; } |
|
static final byte NoOverride(byte level) { return (byte)(level & ~LEVEL_OVERRIDE); } |
|
|
|
|
|
static final int MASK_LTR = |
|
DirPropFlag(L)|DirPropFlag(EN)|DirPropFlag(ENL)|DirPropFlag(ENR)|DirPropFlag(AN)|DirPropFlag(LRE)|DirPropFlag(LRO)|DirPropFlag(LRI); |
|
static final int MASK_RTL = DirPropFlag(R)|DirPropFlag(AL)|DirPropFlag(RLE)|DirPropFlag(RLO)|DirPropFlag(RLI); |
|
|
|
static final int MASK_R_AL = DirPropFlag(R)|DirPropFlag(AL); |
|
|
|
|
|
private static final int MASK_EXPLICIT = DirPropFlag(LRE)|DirPropFlag(LRO)|DirPropFlag(RLE)|DirPropFlag(RLO)|DirPropFlag(PDF); |
|
private static final int MASK_BN_EXPLICIT = DirPropFlag(BN)|MASK_EXPLICIT; |
|
|
|
|
|
private static final int MASK_ISO = DirPropFlag(LRI)|DirPropFlag(RLI)|DirPropFlag(FSI)|DirPropFlag(PDI); |
|
|
|
|
|
private static final int MASK_B_S = DirPropFlag(B)|DirPropFlag(S); |
|
|
|
|
|
static final int MASK_WS = MASK_B_S|DirPropFlag(WS)|MASK_BN_EXPLICIT|MASK_ISO; |
|
|
|
|
|
private static final int MASK_POSSIBLE_N = DirPropFlag(ON)|DirPropFlag(CS)|DirPropFlag(ES)|DirPropFlag(ET)|MASK_WS; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static final int MASK_EMBEDDING = DirPropFlag(NSM)|MASK_POSSIBLE_N; |
|
|
|
|
|
|
|
*/ |
|
private static byte GetLRFromLevel(byte level) |
|
{ |
|
return (byte)(level & 1); |
|
} |
|
|
|
private static boolean IsDefaultLevel(byte level) |
|
{ |
|
return ((level & LEVEL_DEFAULT_LTR) == LEVEL_DEFAULT_LTR); |
|
} |
|
|
|
static boolean IsBidiControlChar(int c) |
|
{ |
|
|
|
0x202a to 0x202e (LRE, RLE, PDF, LRO, RLO) */ |
|
return (((c & 0xfffffffc) == 0x200c) || ((c >= 0x202a) && (c <= 0x202e)) |
|
|| ((c >= 0x2066) && (c <= 0x2069))); |
|
} |
|
|
|
void verifyValidPara() |
|
{ |
|
if (!(this == this.paraBidi)) { |
|
throw new IllegalStateException(); |
|
} |
|
} |
|
|
|
void verifyValidParaOrLine() |
|
{ |
|
BidiBase para = this.paraBidi; |
|
|
|
if (this == para) { |
|
return; |
|
} |
|
|
|
if ((para == null) || (para != para.paraBidi)) { |
|
throw new IllegalStateException(); |
|
} |
|
} |
|
|
|
void verifyRange(int index, int start, int limit) |
|
{ |
|
if (index < start || index >= limit) { |
|
throw new IllegalArgumentException("Value " + index + |
|
" is out of range " + start + " to " + limit); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public BidiBase(int maxLength, int maxRunCount) |
|
{ |
|
|
|
if (maxLength < 0 || maxRunCount < 0) { |
|
throw new IllegalArgumentException(); |
|
} |
|
|
|
/* reset the object, all reference variables null, all flags false, |
|
all sizes 0. |
|
In fact, we don't need to do anything, since class members are |
|
initialized as zero when an instance is created. |
|
*/ |
|
/* |
|
mayAllocateText = false; |
|
mayAllocateRuns = false; |
|
orderParagraphsLTR = false; |
|
paraCount = 0; |
|
runCount = 0; |
|
trailingWSStart = 0; |
|
flags = 0; |
|
paraLevel = 0; |
|
defaultParaLevel = 0; |
|
direction = 0; |
|
*/ |
|
|
|
bdp = UBiDiProps.INSTANCE; |
|
|
|
|
|
if (maxLength > 0) { |
|
getInitialDirPropsMemory(maxLength); |
|
getInitialLevelsMemory(maxLength); |
|
} else { |
|
mayAllocateText = true; |
|
} |
|
|
|
if (maxRunCount > 0) { |
|
|
|
if (maxRunCount > 1) { |
|
getInitialRunsMemory(maxRunCount); |
|
} |
|
} else { |
|
mayAllocateRuns = true; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private Object getMemory(String label, Object array, Class<?> arrayClass, |
|
boolean mayAllocate, int sizeNeeded) |
|
{ |
|
int len = Array.getLength(array); |
|
|
|
|
|
if (sizeNeeded == len) { |
|
return array; |
|
} |
|
if (!mayAllocate) { |
|
|
|
if (sizeNeeded <= len) { |
|
return array; |
|
} |
|
throw new OutOfMemoryError("Failed to allocate memory for " |
|
+ label); |
|
} |
|
/* we may try to grow or shrink */ |
|
|
|
the allocation altogether and rely on this.length */ |
|
try { |
|
return Array.newInstance(arrayClass, sizeNeeded); |
|
} catch (Exception e) { |
|
throw new OutOfMemoryError("Failed to allocate memory for " |
|
+ label); |
|
} |
|
} |
|
|
|
|
|
private void getDirPropsMemory(boolean mayAllocate, int len) |
|
{ |
|
Object array = getMemory("DirProps", dirPropsMemory, Byte.TYPE, mayAllocate, len); |
|
dirPropsMemory = (byte[]) array; |
|
} |
|
|
|
void getDirPropsMemory(int len) |
|
{ |
|
getDirPropsMemory(mayAllocateText, len); |
|
} |
|
|
|
private void getLevelsMemory(boolean mayAllocate, int len) |
|
{ |
|
Object array = getMemory("Levels", levelsMemory, Byte.TYPE, mayAllocate, len); |
|
levelsMemory = (byte[]) array; |
|
} |
|
|
|
void getLevelsMemory(int len) |
|
{ |
|
getLevelsMemory(mayAllocateText, len); |
|
} |
|
|
|
private void getRunsMemory(boolean mayAllocate, int len) |
|
{ |
|
Object array = getMemory("Runs", runsMemory, BidiRun.class, mayAllocate, len); |
|
runsMemory = (BidiRun[]) array; |
|
} |
|
|
|
void getRunsMemory(int len) |
|
{ |
|
getRunsMemory(mayAllocateRuns, len); |
|
} |
|
|
|
|
|
private void getInitialDirPropsMemory(int len) |
|
{ |
|
getDirPropsMemory(true, len); |
|
} |
|
|
|
private void getInitialLevelsMemory(int len) |
|
{ |
|
getLevelsMemory(true, len); |
|
} |
|
|
|
private void getInitialRunsMemory(int len) |
|
{ |
|
getRunsMemory(true, len); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public boolean isInverse() { |
|
return isInverse; |
|
} |
|
|
|
/* perform (P2)..(P3) ------------------------------------------------------- */ |
|
|
|
|
|
|
|
*/ |
|
private void checkParaCount() { |
|
int[] saveLimits; |
|
byte[] saveLevels; |
|
int count = paraCount; |
|
if (count <= paras_level.length) |
|
return; |
|
int oldLength = paras_level.length; |
|
saveLimits = paras_limit; |
|
saveLevels = paras_level; |
|
try { |
|
paras_limit = new int[count * 2]; |
|
paras_level = new byte[count * 2]; |
|
} catch (Exception e) { |
|
throw new OutOfMemoryError("Failed to allocate memory for paras"); |
|
} |
|
System.arraycopy(saveLimits, 0, paras_limit, 0, oldLength); |
|
System.arraycopy(saveLevels, 0, paras_level, 0, oldLength); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static final int NOT_SEEKING_STRONG = 0; |
|
static final int SEEKING_STRONG_FOR_PARA = 1; |
|
static final int SEEKING_STRONG_FOR_FSI = 2; |
|
static final int LOOKING_FOR_PDI = 3; /* 3: found strong after FSI, looking for PDI */ |
|
|
|
private void getDirProps() |
|
{ |
|
int i = 0, i0, i1; |
|
flags = 0; |
|
int uchar; |
|
byte dirProp; |
|
byte defaultParaLevel = 0; |
|
boolean isDefaultLevel = IsDefaultLevel(paraLevel); |
|
|
|
strong R or AL character at either end of the text */ |
|
boolean isDefaultLevelInverse=isDefaultLevel && |
|
(reorderingMode == REORDER_INVERSE_LIKE_DIRECT || |
|
reorderingMode == REORDER_INVERSE_FOR_NUMBERS_SPECIAL); |
|
lastArabicPos = -1; |
|
int controlCount = 0; |
|
boolean removeBidiControls = (reorderingOptions & OPTION_REMOVE_CONTROLS) != 0; |
|
|
|
byte state; |
|
byte lastStrong = ON; /* for default level & inverse Bidi */ |
|
/* The following stacks are used to manage isolate sequences. Those |
|
sequences may be nested, but obviously never more deeply than the |
|
maximum explicit embedding level. |
|
lastStack is the index of the last used entry in the stack. A value of -1 |
|
means that there is no open isolate sequence. |
|
lastStack is reset to -1 on paragraph boundaries. */ |
|
|
|
each open isolate sequence */ |
|
int[] isolateStartStack= new int[MAX_EXPLICIT_LEVEL+1]; |
|
|
|
encountering the initiator of an isolate sequence */ |
|
byte[] previousStateStack = new byte[MAX_EXPLICIT_LEVEL+1]; |
|
int stackLast=-1; |
|
|
|
if ((reorderingOptions & OPTION_STREAMING) != 0) |
|
length = 0; |
|
defaultParaLevel = (byte)(paraLevel & 1); |
|
|
|
if (isDefaultLevel) { |
|
paras_level[0] = defaultParaLevel; |
|
lastStrong = defaultParaLevel; |
|
state = SEEKING_STRONG_FOR_PARA; |
|
} else { |
|
paras_level[0] = paraLevel; |
|
state = NOT_SEEKING_STRONG; |
|
} |
|
/* count paragraphs and determine the paragraph level (P2..P3) */ |
|
/* |
|
* see comment on constant fields: |
|
* the LEVEL_DEFAULT_XXX values are designed so that |
|
* their low-order bit alone yields the intended default |
|
*/ |
|
|
|
for (i = 0; i < originalLength; ) { |
|
i0 = i; |
|
uchar = UTF16.charAt(text, 0, originalLength, i); |
|
i += UTF16.getCharCount(uchar); |
|
i1 = i - 1; /* index of last code unit, gets the directional property */ |
|
|
|
dirProp = (byte)getCustomizedClass(uchar); |
|
flags |= DirPropFlag(dirProp); |
|
dirProps[i1] = dirProp; |
|
if (i1 > i0) { |
|
flags |= DirPropFlag(BN); |
|
do { |
|
dirProps[--i1] = BN; |
|
} while (i1 > i0); |
|
} |
|
if (removeBidiControls && IsBidiControlChar(uchar)) { |
|
controlCount++; |
|
} |
|
if (dirProp == L) { |
|
if (state == SEEKING_STRONG_FOR_PARA) { |
|
paras_level[paraCount - 1] = 0; |
|
state = NOT_SEEKING_STRONG; |
|
} |
|
else if (state == SEEKING_STRONG_FOR_FSI) { |
|
if (stackLast <= MAX_EXPLICIT_LEVEL) { |
|
/* no need for next statement, already set by default */ |
|
|
|
flags |= DirPropFlag(LRI); |
|
} |
|
state = LOOKING_FOR_PDI; |
|
} |
|
lastStrong = L; |
|
continue; |
|
} |
|
if (dirProp == R || dirProp == AL) { |
|
if (state == SEEKING_STRONG_FOR_PARA) { |
|
paras_level[paraCount - 1] = 1; |
|
state = NOT_SEEKING_STRONG; |
|
} |
|
else if (state == SEEKING_STRONG_FOR_FSI) { |
|
if (stackLast <= MAX_EXPLICIT_LEVEL) { |
|
dirProps[isolateStartStack[stackLast]] = RLI; |
|
flags |= DirPropFlag(RLI); |
|
} |
|
state = LOOKING_FOR_PDI; |
|
} |
|
lastStrong = R; |
|
if (dirProp == AL) |
|
lastArabicPos = i - 1; |
|
continue; |
|
} |
|
if (dirProp >= FSI && dirProp <= RLI) { |
|
stackLast++; |
|
if (stackLast <= MAX_EXPLICIT_LEVEL) { |
|
isolateStartStack[stackLast] = i - 1; |
|
previousStateStack[stackLast] = state; |
|
} |
|
if (dirProp == FSI) { |
|
dirProps[i-1] = LRI; |
|
state = SEEKING_STRONG_FOR_FSI; |
|
} |
|
else |
|
state = LOOKING_FOR_PDI; |
|
continue; |
|
} |
|
if (dirProp == PDI) { |
|
if (state == SEEKING_STRONG_FOR_FSI) { |
|
if (stackLast <= MAX_EXPLICIT_LEVEL) { |
|
/* no need for next statement, already set by default */ |
|
|
|
flags |= DirPropFlag(LRI); |
|
} |
|
} |
|
if (stackLast >= 0) { |
|
if (stackLast <= MAX_EXPLICIT_LEVEL) |
|
state = previousStateStack[stackLast]; |
|
stackLast--; |
|
} |
|
continue; |
|
} |
|
if (dirProp == B) { |
|
if (i < originalLength && uchar == CR && text[i] == LF) |
|
continue; |
|
paras_limit[paraCount - 1] = i; |
|
if (isDefaultLevelInverse && lastStrong == R) |
|
paras_level[paraCount - 1] = 1; |
|
if ((reorderingOptions & OPTION_STREAMING) != 0) { |
|
|
|
thus some updates are only done on paragraph boundaries */ |
|
length = i; |
|
this.controlCount = controlCount; |
|
} |
|
if (i < originalLength) { |
|
paraCount++; |
|
checkParaCount(); |
|
if (isDefaultLevel) { |
|
paras_level[paraCount - 1] = defaultParaLevel; |
|
state = SEEKING_STRONG_FOR_PARA; |
|
lastStrong = defaultParaLevel; |
|
} else { |
|
paras_level[paraCount - 1] = paraLevel; |
|
state = NOT_SEEKING_STRONG; |
|
} |
|
stackLast = -1; |
|
} |
|
continue; |
|
} |
|
} |
|
|
|
if (stackLast > MAX_EXPLICIT_LEVEL) { |
|
stackLast = MAX_EXPLICIT_LEVEL; |
|
state=SEEKING_STRONG_FOR_FSI; /* to be on the safe side */ |
|
} |
|
|
|
while (stackLast >= 0) { |
|
if (state == SEEKING_STRONG_FOR_FSI) { |
|
/* no need for next statement, already set by default */ |
|
|
|
flags |= DirPropFlag(LRI); |
|
break; |
|
} |
|
state = previousStateStack[stackLast]; |
|
stackLast--; |
|
} |
|
|
|
if ((reorderingOptions & OPTION_STREAMING) != 0) { |
|
if (length < originalLength) |
|
paraCount--; |
|
} else { |
|
paras_limit[paraCount - 1] = originalLength; |
|
this.controlCount = controlCount; |
|
} |
|
|
|
a strong R or AL at either end of the paragraph */ |
|
if (isDefaultLevelInverse && lastStrong == R) { |
|
paras_level[paraCount - 1] = 1; |
|
} |
|
if (isDefaultLevel) { |
|
paraLevel = paras_level[0]; |
|
} |
|
|
|
paragraphs containing no strong character */ |
|
for (i = 0; i < paraCount; i++) |
|
flags |= DirPropFlagLR(paras_level[i]); |
|
|
|
if (orderParagraphsLTR && (flags & DirPropFlag(B)) != 0) { |
|
flags |= DirPropFlag(L); |
|
} |
|
} |
|
|
|
|
|
byte GetParaLevelAt(int pindex) |
|
{ |
|
if (defaultParaLevel == 0 || pindex < paras_limit[0]) |
|
return paraLevel; |
|
int i; |
|
for (i = 1; i < paraCount; i++) |
|
if (pindex < paras_limit[i]) |
|
break; |
|
if (i >= paraCount) |
|
i = paraCount - 1; |
|
return paras_level[i]; |
|
} |
|
|
|
/* Functions for handling paired brackets ----------------------------------- */ |
|
|
|
/* In the isoRuns array, the first entry is used for text outside of any |
|
isolate sequence. Higher entries are used for each more deeply nested |
|
isolate sequence. isoRunLast is the index of the last used entry. The |
|
openings array is used to note the data of opening brackets not yet |
|
matched by a closing bracket, or matched but still susceptible to change |
|
level. |
|
Each isoRun entry contains the index of the first and |
|
one-after-last openings entries for pending opening brackets it |
|
contains. The next openings entry to use is the one-after-last of the |
|
most deeply nested isoRun entry. |
|
isoRun entries also contain their current embedding level and the last |
|
encountered strong character, since these will be needed to resolve |
|
the level of paired brackets. */ |
|
|
|
private void bracketInit(BracketData bd) { |
|
bd.isoRunLast = 0; |
|
bd.isoRuns[0] = new IsoRun(); |
|
bd.isoRuns[0].start = 0; |
|
bd.isoRuns[0].limit = 0; |
|
bd.isoRuns[0].level = GetParaLevelAt(0); |
|
bd.isoRuns[0].lastStrong = bd.isoRuns[0].lastBase = bd.isoRuns[0].contextDir = (byte)(GetParaLevelAt(0) & 1); |
|
bd.isoRuns[0].contextPos = 0; |
|
bd.openings = new Opening[SIMPLE_PARAS_COUNT]; |
|
bd.isNumbersSpecial = reorderingMode == REORDER_NUMBERS_SPECIAL || |
|
reorderingMode == REORDER_INVERSE_FOR_NUMBERS_SPECIAL; |
|
} |
|
|
|
|
|
private void bracketProcessB(BracketData bd, byte level) { |
|
bd.isoRunLast = 0; |
|
bd.isoRuns[0].limit = 0; |
|
bd.isoRuns[0].level = level; |
|
bd.isoRuns[0].lastStrong = bd.isoRuns[0].lastBase = bd.isoRuns[0].contextDir = (byte)(level & 1); |
|
bd.isoRuns[0].contextPos = 0; |
|
} |
|
|
|
|
|
private void bracketProcessBoundary(BracketData bd, int lastCcPos, |
|
byte contextLevel, byte embeddingLevel) { |
|
IsoRun pLastIsoRun = bd.isoRuns[bd.isoRunLast]; |
|
if ((DirPropFlag(dirProps[lastCcPos]) & MASK_ISO) != 0) |
|
return; |
|
if (NoOverride(embeddingLevel) > NoOverride(contextLevel)) |
|
contextLevel = embeddingLevel; |
|
pLastIsoRun.limit = pLastIsoRun.start; |
|
pLastIsoRun.level = embeddingLevel; |
|
pLastIsoRun.lastStrong = pLastIsoRun.lastBase = pLastIsoRun.contextDir = (byte)(contextLevel & 1); |
|
pLastIsoRun.contextPos = lastCcPos; |
|
} |
|
|
|
|
|
private void bracketProcessLRI_RLI(BracketData bd, byte level) { |
|
IsoRun pLastIsoRun = bd.isoRuns[bd.isoRunLast]; |
|
short lastLimit; |
|
pLastIsoRun.lastBase = ON; |
|
lastLimit = pLastIsoRun.limit; |
|
bd.isoRunLast++; |
|
pLastIsoRun = bd.isoRuns[bd.isoRunLast]; |
|
if (pLastIsoRun == null) |
|
pLastIsoRun = bd.isoRuns[bd.isoRunLast] = new IsoRun(); |
|
pLastIsoRun.start = pLastIsoRun.limit = lastLimit; |
|
pLastIsoRun.level = level; |
|
pLastIsoRun.lastStrong = pLastIsoRun.lastBase = pLastIsoRun.contextDir = (byte)(level & 1); |
|
pLastIsoRun.contextPos = 0; |
|
} |
|
|
|
|
|
private void bracketProcessPDI(BracketData bd) { |
|
IsoRun pLastIsoRun; |
|
bd.isoRunLast--; |
|
pLastIsoRun = bd.isoRuns[bd.isoRunLast]; |
|
pLastIsoRun.lastBase = ON; |
|
} |
|
|
|
|
|
private void bracketAddOpening(BracketData bd, char match, int position) { |
|
IsoRun pLastIsoRun = bd.isoRuns[bd.isoRunLast]; |
|
Opening pOpening; |
|
if (pLastIsoRun.limit >= bd.openings.length) { |
|
Opening[] saveOpenings = bd.openings; |
|
int count; |
|
try { |
|
count = bd.openings.length; |
|
bd.openings = new Opening[count * 2]; |
|
} catch (Exception e) { |
|
throw new OutOfMemoryError("Failed to allocate memory for openings"); |
|
} |
|
System.arraycopy(saveOpenings, 0, bd.openings, 0, count); |
|
} |
|
pOpening = bd.openings[pLastIsoRun.limit]; |
|
if (pOpening == null) |
|
pOpening = bd.openings[pLastIsoRun.limit]= new Opening(); |
|
pOpening.position = position; |
|
pOpening.match = match; |
|
pOpening.contextDir = pLastIsoRun.contextDir; |
|
pOpening.contextPos = pLastIsoRun.contextPos; |
|
pOpening.flags = 0; |
|
pLastIsoRun.limit++; |
|
} |
|
|
|
|
|
private void fixN0c(BracketData bd, int openingIndex, int newPropPosition, byte newProp) { |
|
|
|
IsoRun pLastIsoRun = bd.isoRuns[bd.isoRunLast]; |
|
Opening qOpening; |
|
int k, openingPosition, closingPosition; |
|
for (k = openingIndex+1; k < pLastIsoRun.limit; k++) { |
|
qOpening = bd.openings[k]; |
|
if (qOpening.match >= 0) |
|
continue; |
|
if (newPropPosition < qOpening.contextPos) |
|
break; |
|
if (newPropPosition >= qOpening.position) |
|
continue; |
|
if (newProp == qOpening.contextDir) |
|
break; |
|
openingPosition = qOpening.position; |
|
dirProps[openingPosition] = newProp; |
|
closingPosition = -(qOpening.match); |
|
dirProps[closingPosition] = newProp; |
|
qOpening.match = 0; |
|
fixN0c(bd, k, openingPosition, newProp); |
|
fixN0c(bd, k, closingPosition, newProp); |
|
} |
|
} |
|
|
|
|
|
private byte bracketProcessClosing(BracketData bd, int openIdx, int position) { |
|
IsoRun pLastIsoRun = bd.isoRuns[bd.isoRunLast]; |
|
Opening pOpening, qOpening; |
|
byte direction; |
|
boolean stable; |
|
byte newProp; |
|
pOpening = bd.openings[openIdx]; |
|
direction = (byte)(pLastIsoRun.level & 1); |
|
stable = true; /* assume stable until proved otherwise */ |
|
|
|
/* The stable flag is set when brackets are paired and their |
|
level is resolved and cannot be changed by what will be |
|
found later in the source string. |
|
An unstable match can occur only when applying N0c, where |
|
the resolved level depends on the preceding context, and |
|
this context may be affected by text occurring later. |
|
Example: RTL paragraph containing: abc[(latin) HEBREW] |
|
When the closing parenthesis is encountered, it appears |
|
that N0c1 must be applied since 'abc' sets an opposite |
|
direction context and both parentheses receive level 2. |
|
However, when the closing square bracket is processed, |
|
N0b applies because of 'HEBREW' being included within the |
|
brackets, thus the square brackets are treated like R and |
|
receive level 1. However, this changes the preceding |
|
context of the opening parenthesis, and it now appears |
|
that N0c2 must be applied to the parentheses rather than |
|
N0c1. */ |
|
|
|
if ((direction == 0 && (pOpening.flags & FOUND_L) > 0) || |
|
(direction == 1 && (pOpening.flags & FOUND_R) > 0)) { |
|
newProp = direction; |
|
} |
|
else if ((pOpening.flags & (FOUND_L | FOUND_R)) != 0) { /* N0c */ |
|
|
|
conditions too complicated and not worth checking */ |
|
stable = (openIdx == pLastIsoRun.start); |
|
if (direction != pOpening.contextDir) |
|
newProp = pOpening.contextDir; /* N0c1 */ |
|
else |
|
newProp = direction; /* N0c2 */ |
|
} else { |
|
|
|
pLastIsoRun.limit = (short)openIdx; |
|
return ON; /* N0d */ |
|
} |
|
dirProps[pOpening.position] = newProp; |
|
dirProps[position] = newProp; |
|
|
|
fixN0c(bd, openIdx, pOpening.position, newProp); |
|
if (stable) { |
|
pLastIsoRun.limit = (short)openIdx; /* forget any brackets nested within this pair */ |
|
|
|
while (pLastIsoRun.limit > pLastIsoRun.start && |
|
bd.openings[pLastIsoRun.limit - 1].position == pOpening.position) |
|
pLastIsoRun.limit--; |
|
} else { |
|
int k; |
|
pOpening.match = -position; |
|
|
|
k = openIdx - 1; |
|
while (k >= pLastIsoRun.start && |
|
bd.openings[k].position == pOpening.position) |
|
bd.openings[k--].match = 0; |
|
|
|
this will also neutralize higher located synonyms if any */ |
|
for (k = openIdx + 1; k < pLastIsoRun.limit; k++) { |
|
qOpening =bd.openings[k]; |
|
if (qOpening.position >= position) |
|
break; |
|
if (qOpening.match > 0) |
|
qOpening.match = 0; |
|
} |
|
} |
|
return newProp; |
|
} |
|
|
|
|
|
private void bracketProcessChar(BracketData bd, int position) { |
|
IsoRun pLastIsoRun = bd.isoRuns[bd.isoRunLast]; |
|
byte dirProp, newProp; |
|
byte level; |
|
dirProp = dirProps[position]; |
|
if (dirProp == ON) { |
|
char c, match; |
|
int idx; |
|
|
|
more efficient than checking if it is a closing bracket at all */ |
|
c = text[position]; |
|
for (idx = pLastIsoRun.limit - 1; idx >= pLastIsoRun.start; idx--) { |
|
if (bd.openings[idx].match != c) |
|
continue; |
|
|
|
newProp = bracketProcessClosing(bd, idx, position); |
|
if(newProp == ON) { |
|
c = 0; |
|
break; |
|
} |
|
pLastIsoRun.lastBase = ON; |
|
pLastIsoRun.contextDir = newProp; |
|
pLastIsoRun.contextPos = position; |
|
level = levels[position]; |
|
if ((level & LEVEL_OVERRIDE) != 0) { |
|
short flag; |
|
int i; |
|
newProp = (byte)(level & 1); |
|
pLastIsoRun.lastStrong = newProp; |
|
flag = (short)DirPropFlag(newProp); |
|
for (i = pLastIsoRun.start; i < idx; i++) |
|
bd.openings[i].flags |= flag; |
|
|
|
levels[position] &= ~LEVEL_OVERRIDE; |
|
} |
|
|
|
levels[bd.openings[idx].position] &= ~LEVEL_OVERRIDE; |
|
return; |
|
} |
|
/* We get here only if the ON character is not a matching closing |
|
bracket or it is a case of N0d */ |
|
|
|
if (c != 0) { |
|
match = (char)UCharacter.getBidiPairedBracket(c); /* get the matching char */ |
|
} else { |
|
match = 0; |
|
} |
|
if (match != c && |
|
UCharacter.getIntPropertyValue(c, BIDI_PAIRED_BRACKET_TYPE) == |
|
BidiPairedBracketType.OPEN) { |
|
|
|
create an opening entry for each synonym */ |
|
if (match == 0x232A) { |
|
bracketAddOpening(bd, (char)0x3009, position); |
|
} |
|
else if (match == 0x3009) { |
|
bracketAddOpening(bd, (char)0x232A, position); |
|
} |
|
bracketAddOpening(bd, match, position); |
|
} |
|
} |
|
level = levels[position]; |
|
if ((level & LEVEL_OVERRIDE) != 0) { |
|
newProp = (byte)(level & 1); |
|
if (dirProp != S && dirProp != WS && dirProp != ON) |
|
dirProps[position] = newProp; |
|
pLastIsoRun.lastBase = newProp; |
|
pLastIsoRun.lastStrong = newProp; |
|
pLastIsoRun.contextDir = newProp; |
|
pLastIsoRun.contextPos = position; |
|
} |
|
else if (dirProp <= R || dirProp == AL) { |
|
newProp = DirFromStrong(dirProp); |
|
pLastIsoRun.lastBase = dirProp; |
|
pLastIsoRun.lastStrong = dirProp; |
|
pLastIsoRun.contextDir = newProp; |
|
pLastIsoRun.contextPos = position; |
|
} |
|
else if(dirProp == EN) { |
|
pLastIsoRun.lastBase = EN; |
|
if (pLastIsoRun.lastStrong == L) { |
|
newProp = L; |
|
if (!bd.isNumbersSpecial) |
|
dirProps[position] = ENL; |
|
pLastIsoRun.contextDir = L; |
|
pLastIsoRun.contextPos = position; |
|
} |
|
else { |
|
newProp = R; |
|
if (pLastIsoRun.lastStrong == AL) |
|
dirProps[position] = AN; /* W2 */ |
|
else |
|
dirProps[position] = ENR; |
|
pLastIsoRun.contextDir = R; |
|
pLastIsoRun.contextPos = position; |
|
} |
|
} |
|
else if (dirProp == AN) { |
|
newProp = R; |
|
pLastIsoRun.lastBase = AN; |
|
pLastIsoRun.contextDir = R; |
|
pLastIsoRun.contextPos = position; |
|
} |
|
else if (dirProp == NSM) { |
|
|
|
|
|
may be changed to L or R */ |
|
newProp = pLastIsoRun.lastBase; |
|
if (newProp == ON) |
|
dirProps[position] = newProp; |
|
} |
|
else { |
|
newProp = dirProp; |
|
pLastIsoRun.lastBase = dirProp; |
|
} |
|
if (newProp <= R || newProp == AL) { |
|
int i; |
|
short flag = (short)DirPropFlag(DirFromStrong(newProp)); |
|
for (i = pLastIsoRun.start; i < pLastIsoRun.limit; i++) |
|
if (position > bd.openings[i].position) |
|
bd.openings[i].flags |= flag; |
|
} |
|
} |
|
|
|
/* perform (X1)..(X9) ------------------------------------------------------- */ |
|
|
|
|
|
private byte directionFromFlags() { |
|
|
|
|
|
if (!((flags & MASK_RTL) != 0 || |
|
((flags & DirPropFlag(AN)) != 0 && |
|
(flags & MASK_POSSIBLE_N) != 0))) { |
|
return LTR; |
|
} else if ((flags & MASK_LTR) == 0) { |
|
return RTL; |
|
} else { |
|
return MIXED; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private byte resolveExplicitLevels() { |
|
int i = 0; |
|
byte dirProp; |
|
byte level = GetParaLevelAt(0); |
|
byte dirct; |
|
isolateCount = 0; |
|
|
|
|
|
dirct = directionFromFlags(); |
|
|
|
|
|
if (dirct != MIXED) { |
|
|
|
return dirct; |
|
} |
|
|
|
if (reorderingMode > REORDER_LAST_LOGICAL_TO_VISUAL) { |
|
/* inverse BiDi: mixed, but all characters are at the same embedding level */ |
|
|
|
int paraIndex, start, limit; |
|
for (paraIndex = 0; paraIndex < paraCount; paraIndex++) { |
|
if (paraIndex == 0) |
|
start = 0; |
|
else |
|
start = paras_limit[paraIndex - 1]; |
|
limit = paras_limit[paraIndex]; |
|
level = paras_level[paraIndex]; |
|
for (i = start; i < limit; i++) |
|
levels[i] =level; |
|
} |
|
return dirct; /* no bracket matching for inverse BiDi */ |
|
} |
|
if ((flags & (MASK_EXPLICIT | MASK_ISO)) == 0) { |
|
/* no embeddings, set all levels to the paragraph level */ |
|
|
|
int paraIndex, start, limit; |
|
BracketData bracketData = new BracketData(); |
|
bracketInit(bracketData); |
|
for (paraIndex = 0; paraIndex < paraCount; paraIndex++) { |
|
if (paraIndex == 0) |
|
start = 0; |
|
else |
|
start = paras_limit[paraIndex-1]; |
|
limit = paras_limit[paraIndex]; |
|
level = paras_level[paraIndex]; |
|
for (i = start; i < limit; i++) { |
|
levels[i] = level; |
|
dirProp = dirProps[i]; |
|
if (dirProp == BN) |
|
continue; |
|
if (dirProp == B) { |
|
if ((i + 1) < length) { |
|
if (text[i] == CR && text[i + 1] == LF) |
|
continue; |
|
bracketProcessB(bracketData, level); |
|
} |
|
continue; |
|
} |
|
bracketProcessChar(bracketData, i); |
|
} |
|
} |
|
return dirct; |
|
} |
|
/* continue to perform (Xn) */ |
|
|
|
/* (X1) level is set for all codes, embeddingLevel keeps track of the push/pop operations */ |
|
|
|
byte embeddingLevel = level, newLevel; |
|
byte previousLevel = level; |
|
int lastCcPos = 0; /* index of last effective LRx,RLx, PDx */ |
|
|
|
|
|
stackLast points to its current entry. */ |
|
short[] stack = new short[MAX_EXPLICIT_LEVEL + 2]; |
|
but we need one more entry as base */ |
|
int stackLast = 0; |
|
int overflowIsolateCount = 0; |
|
int overflowEmbeddingCount = 0; |
|
int validIsolateCount = 0; |
|
BracketData bracketData = new BracketData(); |
|
bracketInit(bracketData); |
|
stack[0] = level; /* initialize base entry to para level, no override, no isolate */ |
|
|
|
|
|
flags = 0; |
|
|
|
for (i = 0; i < length; i++) { |
|
dirProp = dirProps[i]; |
|
switch (dirProp) { |
|
case LRE: |
|
case RLE: |
|
case LRO: |
|
case RLO: |
|
|
|
flags |= DirPropFlag(BN); |
|
levels[i] = previousLevel; |
|
if (dirProp == LRE || dirProp == LRO) { |
|
|
|
newLevel = (byte)((embeddingLevel+2) & ~(LEVEL_OVERRIDE | 1)); |
|
} else { |
|
|
|
newLevel = (byte)((NoOverride(embeddingLevel) + 1) | 1); |
|
} |
|
if (newLevel <= MAX_EXPLICIT_LEVEL && overflowIsolateCount == 0 && |
|
overflowEmbeddingCount == 0) { |
|
lastCcPos = i; |
|
embeddingLevel = newLevel; |
|
if (dirProp == LRO || dirProp == RLO) |
|
embeddingLevel |= LEVEL_OVERRIDE; |
|
stackLast++; |
|
stack[stackLast] = embeddingLevel; |
|
/* we don't need to set LEVEL_OVERRIDE off for LRE and RLE |
|
since this has already been done for newLevel which is |
|
the source for embeddingLevel. |
|
*/ |
|
} else { |
|
if (overflowIsolateCount == 0) |
|
overflowEmbeddingCount++; |
|
} |
|
break; |
|
case PDF: |
|
|
|
flags |= DirPropFlag(BN); |
|
levels[i] = previousLevel; |
|
|
|
if (overflowIsolateCount > 0) { |
|
break; |
|
} |
|
if (overflowEmbeddingCount > 0) { |
|
overflowEmbeddingCount--; |
|
break; |
|
} |
|
if (stackLast > 0 && stack[stackLast] < ISOLATE) { |
|
lastCcPos = i; |
|
stackLast--; |
|
embeddingLevel = (byte)stack[stackLast]; |
|
} |
|
break; |
|
case LRI: |
|
case RLI: |
|
flags |= DirPropFlag(ON) | DirPropFlagLR(embeddingLevel); |
|
levels[i] = NoOverride(embeddingLevel); |
|
if (NoOverride(embeddingLevel) != NoOverride(previousLevel)) { |
|
bracketProcessBoundary(bracketData, lastCcPos, |
|
previousLevel, embeddingLevel); |
|
flags |= DirPropFlagMultiRuns; |
|
} |
|
previousLevel = embeddingLevel; |
|
|
|
if (dirProp == LRI) |
|
|
|
newLevel=(byte)((embeddingLevel+2)&~(LEVEL_OVERRIDE|1)); |
|
else |
|
|
|
newLevel=(byte)((NoOverride(embeddingLevel)+1)|1); |
|
if (newLevel <= MAX_EXPLICIT_LEVEL && overflowIsolateCount == 0 |
|
&& overflowEmbeddingCount == 0) { |
|
flags |= DirPropFlag(dirProp); |
|
lastCcPos = i; |
|
validIsolateCount++; |
|
if (validIsolateCount > isolateCount) |
|
isolateCount = validIsolateCount; |
|
embeddingLevel = newLevel; |
|
|
|
will exceed UBIDI_MAX_EXPLICIT_LEVEL before stackLast overflows */ |
|
stackLast++; |
|
stack[stackLast] = (short)(embeddingLevel + ISOLATE); |
|
bracketProcessLRI_RLI(bracketData, embeddingLevel); |
|
} else { |
|
|
|
dirProps[i] = WS; |
|
overflowIsolateCount++; |
|
} |
|
break; |
|
case PDI: |
|
if (NoOverride(embeddingLevel) != NoOverride(previousLevel)) { |
|
bracketProcessBoundary(bracketData, lastCcPos, |
|
previousLevel, embeddingLevel); |
|
flags |= DirPropFlagMultiRuns; |
|
} |
|
|
|
if (overflowIsolateCount > 0) { |
|
overflowIsolateCount--; |
|
|
|
dirProps[i] = WS; |
|
} |
|
else if (validIsolateCount > 0) { |
|
flags |= DirPropFlag(PDI); |
|
lastCcPos = i; |
|
overflowEmbeddingCount = 0; |
|
while (stack[stackLast] < ISOLATE) |
|
stackLast--; |
|
stackLast--; |
|
validIsolateCount--; |
|
bracketProcessPDI(bracketData); |
|
} else |
|
|
|
dirProps[i] = WS; |
|
embeddingLevel = (byte)(stack[stackLast] & ~ISOLATE); |
|
flags |= DirPropFlag(ON) | DirPropFlagLR(embeddingLevel); |
|
previousLevel = embeddingLevel; |
|
levels[i] = NoOverride(embeddingLevel); |
|
break; |
|
case B: |
|
flags |= DirPropFlag(B); |
|
levels[i] = GetParaLevelAt(i); |
|
if ((i + 1) < length) { |
|
if (text[i] == CR && text[i + 1] == LF) |
|
break; |
|
overflowEmbeddingCount = overflowIsolateCount = 0; |
|
validIsolateCount = 0; |
|
stackLast = 0; |
|
previousLevel = embeddingLevel = GetParaLevelAt(i + 1); |
|
stack[0] = embeddingLevel; |
|
bracketProcessB(bracketData, embeddingLevel); |
|
} |
|
break; |
|
case BN: |
|
/* BN, LRE, RLE, and PDF are supposed to be removed (X9) */ |
|
|
|
levels[i] = previousLevel; |
|
flags |= DirPropFlag(BN); |
|
break; |
|
default: |
|
|
|
if (NoOverride(embeddingLevel) != NoOverride(previousLevel)) { |
|
bracketProcessBoundary(bracketData, lastCcPos, |
|
previousLevel, embeddingLevel); |
|
flags |= DirPropFlagMultiRuns; |
|
if ((embeddingLevel & LEVEL_OVERRIDE) != 0) |
|
flags |= DirPropFlagO(embeddingLevel); |
|
else |
|
flags |= DirPropFlagE(embeddingLevel); |
|
} |
|
previousLevel = embeddingLevel; |
|
levels[i] = embeddingLevel; |
|
bracketProcessChar(bracketData, i); |
|
|
|
flags |= DirPropFlag(dirProps[i]); |
|
break; |
|
} |
|
} |
|
if ((flags & MASK_EMBEDDING) != 0) { |
|
flags |= DirPropFlagLR(paraLevel); |
|
} |
|
if (orderParagraphsLTR && (flags & DirPropFlag(B)) != 0) { |
|
flags |= DirPropFlag(L); |
|
} |
|
|
|
dirct = directionFromFlags(); |
|
|
|
return dirct; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private byte checkExplicitLevels() { |
|
byte dirProp; |
|
int i; |
|
int isolateCount = 0; |
|
|
|
this.flags = 0; |
|
byte level; |
|
this.isolateCount = 0; |
|
|
|
for (i = 0; i < length; ++i) { |
|
if (levels[i] == 0) { |
|
levels[i] = paraLevel; |
|
} |
|
|
|
|
|
if (MAX_EXPLICIT_LEVEL < (levels[i]&0x7f)) { |
|
if ((levels[i] & LEVEL_OVERRIDE) != 0) { |
|
levels[i] = (byte)(paraLevel|LEVEL_OVERRIDE); |
|
} else { |
|
levels[i] = paraLevel; |
|
} |
|
} |
|
|
|
level = levels[i]; |
|
dirProp = dirProps[i]; |
|
if (dirProp == LRI || dirProp == RLI) { |
|
isolateCount++; |
|
if (isolateCount > this.isolateCount) |
|
this.isolateCount = isolateCount; |
|
} |
|
else if (dirProp == PDI) { |
|
isolateCount--; |
|
} else if (dirProp == B) { |
|
isolateCount = 0; |
|
} |
|
if ((level & LEVEL_OVERRIDE) != 0) { |
|
|
|
level &= ~LEVEL_OVERRIDE; |
|
flags |= DirPropFlagO(level); |
|
} else { |
|
|
|
flags |= DirPropFlagE(level) | DirPropFlag(dirProp); |
|
} |
|
if ((level < GetParaLevelAt(i) && |
|
!((0 == level) && (dirProp == B))) || |
|
(MAX_EXPLICIT_LEVEL < level)) { |
|
|
|
throw new IllegalArgumentException("level " + level + |
|
" out of bounds at " + i); |
|
} |
|
} |
|
if ((flags & MASK_EMBEDDING) != 0) { |
|
flags |= DirPropFlagLR(paraLevel); |
|
} |
|
|
|
return directionFromFlags(); |
|
} |
|
|
|
/*********************************************************************/ |
|
/* The Properties state machine table */ |
|
/*********************************************************************/ |
|
/* */ |
|
/* All table cells are 8 bits: */ |
|
/* bits 0..4: next state */ |
|
/* bits 5..7: action to perform (if > 0) */ |
|
/* */ |
|
/* Cells may be of format "n" where n represents the next state */ |
|
/* (except for the rightmost column). */ |
|
/* Cells may also be of format "_(x,y)" where x represents an action */ |
|
/* to perform and y represents the next state. */ |
|
/* */ |
|
/*********************************************************************/ |
|
/* Definitions and type for properties state tables */ |
|
|
|
private static final int IMPTABPROPS_COLUMNS = 16; |
|
private static final int IMPTABPROPS_RES = IMPTABPROPS_COLUMNS - 1; |
|
private static short GetStateProps(short cell) { |
|
return (short)(cell & 0x1f); |
|
} |
|
private static short GetActionProps(short cell) { |
|
return (short)(cell >> 5); |
|
} |
|
|
|
private static final short groupProp[] = |
|
{ |
|
|
|
0, 1, 2, 7, 8, 3, 9, 6, 5, 4, 4, 10, 10, 12, 10, 10, 10, 11, 10, 4, 4, 4, 4, 13, 14 |
|
}; |
|
private static final short _L = 0; |
|
private static final short _R = 1; |
|
private static final short _EN = 2; |
|
private static final short _AN = 3; |
|
private static final short _ON = 4; |
|
private static final short _S = 5; |
|
private static final short _B = 6; /* reduced dirProp */ |
|
|
|
/*********************************************************************/ |
|
/* */ |
|
/* PROPERTIES STATE TABLE */ |
|
/* */ |
|
/* In table impTabProps, */ |
|
/* - the ON column regroups ON and WS, FSI, RLI, LRI and PDI */ |
|
/* - the BN column regroups BN, LRE, RLE, LRO, RLO, PDF */ |
|
/* - the Res column is the reduced property assigned to a run */ |
|
/* */ |
|
/* Action 1: process current run1, init new run1 */ |
|
/* 2: init new run2 */ |
|
/* 3: process run1, process run2, init new run1 */ |
|
/* 4: process run1, set run1=run2, init new run2 */ |
|
/* */ |
|
/* Notes: */ |
|
/* 1) This table is used in resolveImplicitLevels(). */ |
|
/* 2) This table triggers actions when there is a change in the Bidi*/ |
|
/* property of incoming characters (action 1). */ |
|
/* 3) Most such property sequences are processed immediately (in */ |
|
/* fact, passed to processPropertySeq(). */ |
|
/* 4) However, numbers are assembled as one sequence. This means */ |
|
/* that undefined situations (like CS following digits, until */ |
|
/* it is known if the next char will be a digit) are held until */ |
|
/* following chars define them. */ |
|
/* Example: digits followed by CS, then comes another CS or ON; */ |
|
/* the digits will be processed, then the CS assigned */ |
|
/* as the start of an ON sequence (action 3). */ |
|
/* 5) There are cases where more than one sequence must be */ |
|
/* processed, for instance digits followed by CS followed by L: */ |
|
/* the digits must be processed as one sequence, and the CS */ |
|
/* must be processed as an ON sequence, all this before starting */ |
|
/* assembling chars for the opening L sequence. */ |
|
/* */ |
|
|
|
private static final short impTabProps[][] = |
|
{ |
|
/* L, R, EN, AN, ON, S, B, ES, ET, CS, BN, NSM, AL, ENL, ENR, Res */ |
|
{ 1, 2, 4, 5, 7, 15, 17, 7, 9, 7, 0, 7, 3, 18, 21, _ON }, |
|
{ 1, 32+2, 32+4, 32+5, 32+7, 32+15, 32+17, 32+7, 32+9, 32+7, 1, 1, 32+3, 32+18, 32+21, _L }, |
|
{ 32+1, 2, 32+4, 32+5, 32+7, 32+15, 32+17, 32+7, 32+9, 32+7, 2, 2, 32+3, 32+18, 32+21, _R }, |
|
{ 32+1, 32+2, 32+6, 32+6, 32+8, 32+16, 32+17, 32+8, 32+8, 32+8, 3, 3, 3, 32+18, 32+21, _R }, |
|
{ 32+1, 32+2, 4, 32+5, 32+7, 32+15, 32+17, 64+10, 11, 64+10, 4, 4, 32+3, 18, 21, _EN }, |
|
{ 32+1, 32+2, 32+4, 5, 32+7, 32+15, 32+17, 32+7, 32+9, 64+12, 5, 5, 32+3, 32+18, 32+21, _AN }, |
|
{ 32+1, 32+2, 6, 6, 32+8, 32+16, 32+17, 32+8, 32+8, 64+13, 6, 6, 32+3, 18, 21, _AN }, |
|
{ 32+1, 32+2, 32+4, 32+5, 7, 32+15, 32+17, 7, 64+14, 7, 7, 7, 32+3, 32+18, 32+21, _ON }, |
|
{ 32+1, 32+2, 32+6, 32+6, 8, 32+16, 32+17, 8, 8, 8, 8, 8, 32+3, 32+18, 32+21, _ON }, |
|
{ 32+1, 32+2, 4, 32+5, 7, 32+15, 32+17, 7, 9, 7, 9, 9, 32+3, 18, 21, _ON }, |
|
{ 96+1, 96+2, 4, 96+5, 128+7, 96+15, 96+17, 128+7,128+14, 128+7, 10, 128+7, 96+3, 18, 21, _EN }, |
|
{ 32+1, 32+2, 4, 32+5, 32+7, 32+15, 32+17, 32+7, 11, 32+7, 11, 11, 32+3, 18, 21, _EN }, |
|
{ 96+1, 96+2, 96+4, 5, 128+7, 96+15, 96+17, 128+7,128+14, 128+7, 12, 128+7, 96+3, 96+18, 96+21, _AN }, |
|
{ 96+1, 96+2, 6, 6, 128+8, 96+16, 96+17, 128+8, 128+8, 128+8, 13, 128+8, 96+3, 18, 21, _AN }, |
|
{ 32+1, 32+2, 128+4, 32+5, 7, 32+15, 32+17, 7, 14, 7, 14, 14, 32+3,128+18,128+21, _ON }, |
|
{ 32+1, 32+2, 32+4, 32+5, 32+7, 15, 32+17, 32+7, 32+9, 32+7, 15, 32+7, 32+3, 32+18, 32+21, _S }, |
|
{ 32+1, 32+2, 32+6, 32+6, 32+8, 16, 32+17, 32+8, 32+8, 32+8, 16, 32+8, 32+3, 32+18, 32+21, _S }, |
|
{ 32+1, 32+2, 32+4, 32+5, 32+7, 32+15, 17, 32+7, 32+9, 32+7, 17, 32+7, 32+3, 32+18, 32+21, _B }, |
|
{ 32+1, 32+2, 18, 32+5, 32+7, 32+15, 32+17, 64+19, 20, 64+19, 18, 18, 32+3, 18, 21, _L }, |
|
{ 96+1, 96+2, 18, 96+5, 128+7, 96+15, 96+17, 128+7,128+14, 128+7, 19, 128+7, 96+3, 18, 21, _L }, |
|
{ 32+1, 32+2, 18, 32+5, 32+7, 32+15, 32+17, 32+7, 20, 32+7, 20, 20, 32+3, 18, 21, _L }, |
|
{ 32+1, 32+2, 21, 32+5, 32+7, 32+15, 32+17, 64+22, 23, 64+22, 21, 21, 32+3, 18, 21, _AN }, |
|
{ 96+1, 96+2, 21, 96+5, 128+7, 96+15, 96+17, 128+7,128+14, 128+7, 22, 128+7, 96+3, 18, 21, _AN }, |
|
{ 32+1, 32+2, 21, 32+5, 32+7, 32+15, 32+17, 32+7, 23, 32+7, 23, 23, 32+3, 18, 21, _AN } |
|
}; |
|
|
|
/*********************************************************************/ |
|
/* The levels state machine tables */ |
|
/*********************************************************************/ |
|
/* */ |
|
/* All table cells are 8 bits: */ |
|
/* bits 0..3: next state */ |
|
/* bits 4..7: action to perform (if > 0) */ |
|
/* */ |
|
/* Cells may be of format "n" where n represents the next state */ |
|
/* (except for the rightmost column). */ |
|
/* Cells may also be of format "_(x,y)" where x represents an action */ |
|
/* to perform and y represents the next state. */ |
|
/* */ |
|
/* This format limits each table to 16 states each and to 15 actions.*/ |
|
/* */ |
|
/*********************************************************************/ |
|
/* Definitions and type for levels state tables */ |
|
|
|
private static final int IMPTABLEVELS_COLUMNS = _B + 2; |
|
private static final int IMPTABLEVELS_RES = IMPTABLEVELS_COLUMNS - 1; |
|
private static short GetState(byte cell) { return (short)(cell & 0x0f); } |
|
private static short GetAction(byte cell) { return (short)(cell >> 4); } |
|
|
|
private static class ImpTabPair { |
|
byte[][][] imptab; |
|
short[][] impact; |
|
|
|
ImpTabPair(byte[][] table1, byte[][] table2, |
|
short[] act1, short[] act2) { |
|
imptab = new byte[][][] {table1, table2}; |
|
impact = new short[][] {act1, act2}; |
|
} |
|
} |
|
|
|
/*********************************************************************/ |
|
/* */ |
|
/* LEVELS STATE TABLES */ |
|
/* */ |
|
/* In all levels state tables, */ |
|
/* - state 0 is the initial state */ |
|
/* - the Res column is the increment to add to the text level */ |
|
/* for this property sequence. */ |
|
/* */ |
|
/* The impact arrays for each table of a pair map the local action */ |
|
/* numbers of the table to the total list of actions. For instance, */ |
|
/* action 2 in a given table corresponds to the action number which */ |
|
/* appears in entry [2] of the impact array for that table. */ |
|
/* The first entry of all impact arrays must be 0. */ |
|
/* */ |
|
/* Action 1: init conditional sequence */ |
|
/* 2: prepend conditional sequence to current sequence */ |
|
/* 3: set ON sequence to new level - 1 */ |
|
/* 4: init EN/AN/ON sequence */ |
|
/* 5: fix EN/AN/ON sequence followed by R */ |
|
/* 6: set previous level sequence to level 2 */ |
|
/* */ |
|
/* Notes: */ |
|
/* 1) These tables are used in processPropertySeq(). The input */ |
|
/* is property sequences as determined by resolveImplicitLevels. */ |
|
/* 2) Most such property sequences are processed immediately */ |
|
/* (levels are assigned). */ |
|
/* 3) However, some sequences cannot be assigned a final level till */ |
|
/* one or more following sequences are received. For instance, */ |
|
/* ON following an R sequence within an even-level paragraph. */ |
|
/* If the following sequence is R, the ON sequence will be */ |
|
/* assigned basic run level+1, and so will the R sequence. */ |
|
/* 4) S is generally handled like ON, since its level will be fixed */ |
|
/* to paragraph level in adjustWSLevels(). */ |
|
/* */ |
|
|
|
private static final byte impTabL_DEFAULT[][] = /* Even paragraph level */ |
|
|
|
|
|
*/ |
|
{ |
|
/* L, R, EN, AN, ON, S, B, Res */ |
|
{ 0, 1, 0, 2, 0, 0, 0, 0 }, |
|
{ 0, 1, 3, 3, 0x14, 0x14, 0, 1 }, |
|
{ 0, 1, 0, 2, 0x15, 0x15, 0, 2 }, |
|
{ 0, 1, 3, 3, 0x14, 0x14, 0, 2 }, |
|
{ 0, 0x21, 0x33, 0x33, 4, 4, 0, 0 }, |
|
{ 0, 0x21, 0, 0x32, 5, 5, 0, 0 } |
|
}; |
|
|
|
private static final byte impTabR_DEFAULT[][] = /* Odd paragraph level */ |
|
|
|
|
|
*/ |
|
{ |
|
/* L, R, EN, AN, ON, S, B, Res */ |
|
{ 1, 0, 2, 2, 0, 0, 0, 0 }, |
|
{ 1, 0, 1, 3, 0x14, 0x14, 0, 1 }, |
|
{ 1, 0, 2, 2, 0, 0, 0, 1 }, |
|
{ 1, 0, 1, 3, 5, 5, 0, 1 }, |
|
{ 0x21, 0, 0x21, 3, 4, 4, 0, 0 }, |
|
{ 1, 0, 1, 3, 5, 5, 0, 0 } |
|
}; |
|
|
|
private static final short[] impAct0 = {0,1,2,3,4}; |
|
|
|
private static final ImpTabPair impTab_DEFAULT = new ImpTabPair( |
|
impTabL_DEFAULT, impTabR_DEFAULT, impAct0, impAct0); |
|
|
|
private static final byte impTabL_NUMBERS_SPECIAL[][] = { /* Even paragraph level */ |
|
/* In this table, conditional sequences receive the lower possible |
|
level until proven otherwise. |
|
*/ |
|
/* L, R, EN, AN, ON, S, B, Res */ |
|
{ 0, 2, 0x11, 0x11, 0, 0, 0, 0 }, |
|
{ 0, 0x42, 1, 1, 0, 0, 0, 0 }, |
|
{ 0, 2, 4, 4, 0x13, 0x13, 0, 1 }, |
|
{ 0, 0x22, 0x34, 0x34, 3, 3, 0, 0 }, |
|
{ 0, 2, 4, 4, 0x13, 0x13, 0, 2 } |
|
}; |
|
private static final ImpTabPair impTab_NUMBERS_SPECIAL = new ImpTabPair( |
|
impTabL_NUMBERS_SPECIAL, impTabR_DEFAULT, impAct0, impAct0); |
|
|
|
private static final byte impTabL_GROUP_NUMBERS_WITH_R[][] = { |
|
/* In this table, EN/AN+ON sequences receive levels as if associated with R |
|
until proven that there is L or sor/eor on both sides. AN is handled like EN. |
|
*/ |
|
/* L, R, EN, AN, ON, S, B, Res */ |
|
{ 0, 3, 0x11, 0x11, 0, 0, 0, 0 }, |
|
{ 0x20, 3, 1, 1, 2, 0x20, 0x20, 2 }, |
|
{ 0x20, 3, 1, 1, 2, 0x20, 0x20, 1 }, |
|
{ 0, 3, 5, 5, 0x14, 0, 0, 1 }, |
|
{ 0x20, 3, 5, 5, 4, 0x20, 0x20, 1 }, |
|
{ 0, 3, 5, 5, 0x14, 0, 0, 2 } |
|
}; |
|
private static final byte impTabR_GROUP_NUMBERS_WITH_R[][] = { |
|
/* In this table, EN/AN+ON sequences receive levels as if associated with R |
|
until proven that there is L on both sides. AN is handled like EN. |
|
*/ |
|
/* L, R, EN, AN, ON, S, B, Res */ |
|
{ 2, 0, 1, 1, 0, 0, 0, 0 }, |
|
{ 2, 0, 1, 1, 0, 0, 0, 1 }, |
|
{ 2, 0, 0x14, 0x14, 0x13, 0, 0, 1 }, |
|
{ 0x22, 0, 4, 4, 3, 0, 0, 0 }, |
|
{ 0x22, 0, 4, 4, 3, 0, 0, 1 } |
|
}; |
|
private static final ImpTabPair impTab_GROUP_NUMBERS_WITH_R = new |
|
ImpTabPair(impTabL_GROUP_NUMBERS_WITH_R, |
|
impTabR_GROUP_NUMBERS_WITH_R, impAct0, impAct0); |
|
|
|
private static final byte impTabL_INVERSE_NUMBERS_AS_L[][] = { |
|
/* This table is identical to the Default LTR table except that EN and AN |
|
are handled like L. |
|
*/ |
|
/* L, R, EN, AN, ON, S, B, Res */ |
|
{ 0, 1, 0, 0, 0, 0, 0, 0 }, |
|
{ 0, 1, 0, 0, 0x14, 0x14, 0, 1 }, |
|
{ 0, 1, 0, 0, 0x15, 0x15, 0, 2 }, |
|
{ 0, 1, 0, 0, 0x14, 0x14, 0, 2 }, |
|
{ 0x20, 1, 0x20, 0x20, 4, 4, 0x20, 1 }, |
|
{ 0x20, 1, 0x20, 0x20, 5, 5, 0x20, 1 } |
|
}; |
|
private static final byte impTabR_INVERSE_NUMBERS_AS_L[][] = { |
|
/* This table is identical to the Default RTL table except that EN and AN |
|
are handled like L. |
|
*/ |
|
/* L, R, EN, AN, ON, S, B, Res */ |
|
{ 1, 0, 1, 1, 0, 0, 0, 0 }, |
|
{ 1, 0, 1, 1, 0x14, 0x14, 0, 1 }, |
|
{ 1, 0, 1, 1, 0, 0, 0, 1 }, |
|
{ 1, 0, 1, 1, 5, 5, 0, 1 }, |
|
{ 0x21, 0, 0x21, 0x21, 4, 4, 0, 0 }, |
|
{ 1, 0, 1, 1, 5, 5, 0, 0 } |
|
}; |
|
private static final ImpTabPair impTab_INVERSE_NUMBERS_AS_L = new ImpTabPair |
|
(impTabL_INVERSE_NUMBERS_AS_L, impTabR_INVERSE_NUMBERS_AS_L, |
|
impAct0, impAct0); |
|
|
|
private static final byte impTabR_INVERSE_LIKE_DIRECT[][] = { /* Odd paragraph level */ |
|
/* In this table, conditional sequences receive the lower possible level |
|
until proven otherwise. |
|
*/ |
|
/* L, R, EN, AN, ON, S, B, Res */ |
|
{ 1, 0, 2, 2, 0, 0, 0, 0 }, |
|
{ 1, 0, 1, 2, 0x13, 0x13, 0, 1 }, |
|
{ 1, 0, 2, 2, 0, 0, 0, 1 }, |
|
{ 0x21, 0x30, 6, 4, 3, 3, 0x30, 0 }, |
|
{ 0x21, 0x30, 6, 4, 5, 5, 0x30, 3 }, |
|
{ 0x21, 0x30, 6, 4, 5, 5, 0x30, 2 }, |
|
{ 0x21, 0x30, 6, 4, 3, 3, 0x30, 1 } |
|
}; |
|
private static final short[] impAct1 = {0,1,13,14}; |
|
private static final ImpTabPair impTab_INVERSE_LIKE_DIRECT = new ImpTabPair( |
|
impTabL_DEFAULT, impTabR_INVERSE_LIKE_DIRECT, impAct0, impAct1); |
|
|
|
private static final byte impTabL_INVERSE_LIKE_DIRECT_WITH_MARKS[][] = { |
|
/* The case handled in this table is (visually): R EN L |
|
*/ |
|
/* L, R, EN, AN, ON, S, B, Res */ |
|
{ 0, 0x63, 0, 1, 0, 0, 0, 0 }, |
|
{ 0, 0x63, 0, 1, 0x12, 0x30, 0, 4 }, |
|
{ 0x20, 0x63, 0x20, 1, 2, 0x30, 0x20, 3 }, |
|
{ 0, 0x63, 0x55, 0x56, 0x14, 0x30, 0, 3 }, |
|
{ 0x30, 0x43, 0x55, 0x56, 4, 0x30, 0x30, 3 }, |
|
{ 0x30, 0x43, 5, 0x56, 0x14, 0x30, 0x30, 4 }, |
|
{ 0x30, 0x43, 0x55, 6, 0x14, 0x30, 0x30, 4 } |
|
}; |
|
private static final byte impTabR_INVERSE_LIKE_DIRECT_WITH_MARKS[][] = { |
|
/* The cases handled in this table are (visually): R EN L |
|
R L AN L |
|
*/ |
|
/* L, R, EN, AN, ON, S, B, Res */ |
|
{ 0x13, 0, 1, 1, 0, 0, 0, 0 }, |
|
{ 0x23, 0, 1, 1, 2, 0x40, 0, 1 }, |
|
{ 0x23, 0, 1, 1, 2, 0x40, 0, 0 }, |
|
{ 3, 0, 3, 0x36, 0x14, 0x40, 0, 1 }, |
|
{ 0x53, 0x40, 5, 0x36, 4, 0x40, 0x40, 0 }, |
|
{ 0x53, 0x40, 5, 0x36, 4, 0x40, 0x40, 1 }, |
|
{ 0x53, 0x40, 6, 6, 4, 0x40, 0x40, 3 } |
|
}; |
|
private static final short[] impAct2 = {0,1,2,5,6,7,8}; |
|
private static final short[] impAct3 = {0,1,9,10,11,12}; |
|
private static final ImpTabPair impTab_INVERSE_LIKE_DIRECT_WITH_MARKS = |
|
new ImpTabPair(impTabL_INVERSE_LIKE_DIRECT_WITH_MARKS, |
|
impTabR_INVERSE_LIKE_DIRECT_WITH_MARKS, impAct2, impAct3); |
|
|
|
private static final ImpTabPair impTab_INVERSE_FOR_NUMBERS_SPECIAL = new ImpTabPair( |
|
impTabL_NUMBERS_SPECIAL, impTabR_INVERSE_LIKE_DIRECT, impAct0, impAct1); |
|
|
|
private static final byte impTabL_INVERSE_FOR_NUMBERS_SPECIAL_WITH_MARKS[][] = { |
|
/* The case handled in this table is (visually): R EN L |
|
*/ |
|
/* L, R, EN, AN, ON, S, B, Res */ |
|
{ 0, 0x62, 1, 1, 0, 0, 0, 0 }, |
|
{ 0, 0x62, 1, 1, 0, 0x30, 0, 4 }, |
|
{ 0, 0x62, 0x54, 0x54, 0x13, 0x30, 0, 3 }, |
|
{ 0x30, 0x42, 0x54, 0x54, 3, 0x30, 0x30, 3 }, |
|
{ 0x30, 0x42, 4, 4, 0x13, 0x30, 0x30, 4 } |
|
}; |
|
private static final ImpTabPair impTab_INVERSE_FOR_NUMBERS_SPECIAL_WITH_MARKS = new |
|
ImpTabPair(impTabL_INVERSE_FOR_NUMBERS_SPECIAL_WITH_MARKS, |
|
impTabR_INVERSE_LIKE_DIRECT_WITH_MARKS, impAct2, impAct3); |
|
|
|
private static class LevState { |
|
byte[][] impTab; |
|
short[] impAct; |
|
int startON; |
|
int startL2EN; |
|
int lastStrongRTL; |
|
int runStart; |
|
short state; |
|
byte runLevel; /* run level before implicit solving */ |
|
} |
|
|
|
/*------------------------------------------------------------------------*/ |
|
|
|
static final int FIRSTALLOC = 10; |
|
|
|
|
|
|
|
*/ |
|
private void addPoint(int pos, int flag) |
|
{ |
|
Point point = new Point(); |
|
|
|
int len = insertPoints.points.length; |
|
if (len == 0) { |
|
insertPoints.points = new Point[FIRSTALLOC]; |
|
len = FIRSTALLOC; |
|
} |
|
if (insertPoints.size >= len) { |
|
Point[] savePoints = insertPoints.points; |
|
insertPoints.points = new Point[len * 2]; |
|
System.arraycopy(savePoints, 0, insertPoints.points, 0, len); |
|
} |
|
point.pos = pos; |
|
point.flag = flag; |
|
insertPoints.points[insertPoints.size] = point; |
|
insertPoints.size++; |
|
} |
|
|
|
private void setLevelsOutsideIsolates(int start, int limit, byte level) |
|
{ |
|
byte dirProp; |
|
int isolateCount = 0, k; |
|
for (k = start; k < limit; k++) { |
|
dirProp = dirProps[k]; |
|
if (dirProp == PDI) |
|
isolateCount--; |
|
if (isolateCount == 0) { |
|
levels[k] = level; |
|
} |
|
if (dirProp == LRI || dirProp == RLI) |
|
isolateCount++; |
|
} |
|
} |
|
|
|
/* perform rules (Wn), (Nn), and (In) on a run of the text ------------------ */ |
|
|
|
/* |
|
* This implementation of the (Wn) rules applies all rules in one pass. |
|
* In order to do so, it needs a look-ahead of typically 1 character |
|
* (except for W5: sequences of ET) and keeps track of changes |
|
* in a rule Wp that affect a later Wq (p<q). |
|
* |
|
* The (Nn) and (In) rules are also performed in that same single loop, |
|
* but effectively one iteration behind for white space. |
|
* |
|
* Since all implicit rules are performed in one step, it is not necessary |
|
* to actually store the intermediate directional properties in dirProps[]. |
|
*/ |
|
|
|
private void processPropertySeq(LevState levState, short _prop, |
|
int start, int limit) { |
|
byte cell; |
|
byte[][] impTab = levState.impTab; |
|
short[] impAct = levState.impAct; |
|
short oldStateSeq,actionSeq; |
|
byte level, addLevel; |
|
int start0, k; |
|
|
|
start0 = start; |
|
oldStateSeq = levState.state; |
|
cell = impTab[oldStateSeq][_prop]; |
|
levState.state = GetState(cell); |
|
actionSeq = impAct[GetAction(cell)]; |
|
addLevel = impTab[levState.state][IMPTABLEVELS_RES]; |
|
|
|
if (actionSeq != 0) { |
|
switch (actionSeq) { |
|
case 1: |
|
levState.startON = start0; |
|
break; |
|
|
|
case 2: |
|
start = levState.startON; |
|
break; |
|
|
|
case 3: |
|
level = (byte)(levState.runLevel + 1); |
|
setLevelsOutsideIsolates(levState.startON, start0, level); |
|
break; |
|
|
|
case 4: |
|
level = (byte)(levState.runLevel + 2); |
|
setLevelsOutsideIsolates(levState.startON, start0, level); |
|
break; |
|
|
|
case 5: /* L or S after possible relevant EN/AN */ |
|
|
|
if (levState.startL2EN >= 0) { |
|
addPoint(levState.startL2EN, LRM_BEFORE); |
|
} |
|
levState.startL2EN = -1; /* not within previous if since could also be -2 */ |
|
|
|
if ((insertPoints.points.length == 0) || |
|
(insertPoints.size <= insertPoints.confirmed)) { |
|
|
|
levState.lastStrongRTL = -1; |
|
|
|
level = impTab[oldStateSeq][IMPTABLEVELS_RES]; |
|
if ((level & 1) != 0 && levState.startON > 0) { |
|
start = levState.startON; /* reset to basic run level */ |
|
} |
|
if (_prop == _S) { |
|
addPoint(start0, LRM_BEFORE); |
|
insertPoints.confirmed = insertPoints.size; |
|
} |
|
break; |
|
} |
|
|
|
for (k = levState.lastStrongRTL + 1; k < start0; k++) { |
|
|
|
levels[k] = (byte)((levels[k] - 2) & ~1); |
|
} |
|
|
|
insertPoints.confirmed = insertPoints.size; |
|
levState.lastStrongRTL = -1; |
|
if (_prop == _S) { |
|
addPoint(start0, LRM_BEFORE); |
|
insertPoints.confirmed = insertPoints.size; |
|
} |
|
break; |
|
|
|
case 6: /* R/AL after possible relevant EN/AN */ |
|
|
|
if (insertPoints.points.length > 0) |
|
|
|
insertPoints.size = insertPoints.confirmed; |
|
levState.startON = -1; |
|
levState.startL2EN = -1; |
|
levState.lastStrongRTL = limit - 1; |
|
break; |
|
|
|
case 7: /* EN/AN after R/AL + possible cont */ |
|
/* check for real AN */ |
|
|
|
if ((_prop == _AN) && (dirProps[start0] == AN) && |
|
(reorderingMode != REORDER_INVERSE_FOR_NUMBERS_SPECIAL)) |
|
{ |
|
|
|
if (levState.startL2EN == -1) { /* if no relevant EN already found */ |
|
|
|
levState.lastStrongRTL = limit - 1; |
|
break; |
|
} |
|
if (levState.startL2EN >= 0) { |
|
addPoint(levState.startL2EN, LRM_BEFORE); |
|
levState.startL2EN = -2; |
|
} |
|
|
|
addPoint(start0, LRM_BEFORE); |
|
break; |
|
} |
|
|
|
if (levState.startL2EN == -1) { |
|
levState.startL2EN = start0; |
|
} |
|
break; |
|
|
|
case 8: |
|
levState.lastStrongRTL = limit - 1; |
|
levState.startON = -1; |
|
break; |
|
|
|
case 9: /* L after R+ON/EN/AN */ |
|
|
|
for (k = start0-1; k >= 0 && ((levels[k] & 1) == 0); k--) { |
|
} |
|
if (k >= 0) { |
|
addPoint(k, RLM_BEFORE); |
|
insertPoints.confirmed = insertPoints.size; /* confirm it */ |
|
} |
|
levState.startON = start0; |
|
break; |
|
|
|
case 10: /* AN after L */ |
|
/* AN numbers between L text on both sides may be trouble. */ |
|
|
|
addPoint(start0, LRM_BEFORE); |
|
addPoint(start0, LRM_AFTER); |
|
break; |
|
|
|
case 11: /* R after L+ON/EN/AN */ |
|
|
|
insertPoints.size=insertPoints.confirmed; |
|
if (_prop == _S) { |
|
addPoint(start0, RLM_BEFORE); |
|
insertPoints.confirmed = insertPoints.size; |
|
} |
|
break; |
|
|
|
case 12: |
|
level = (byte)(levState.runLevel + addLevel); |
|
for (k=levState.startON; k < start0; k++) { |
|
if (levels[k] < level) { |
|
levels[k] = level; |
|
} |
|
} |
|
insertPoints.confirmed = insertPoints.size; |
|
levState.startON = start0; |
|
break; |
|
|
|
case 13: |
|
level = levState.runLevel; |
|
for (k = start0-1; k >= levState.startON; k--) { |
|
if (levels[k] == level+3) { |
|
while (levels[k] == level+3) { |
|
levels[k--] -= 2; |
|
} |
|
while (levels[k] == level) { |
|
k--; |
|
} |
|
} |
|
if (levels[k] == level+2) { |
|
levels[k] = level; |
|
continue; |
|
} |
|
levels[k] = (byte)(level+1); |
|
} |
|
break; |
|
|
|
case 14: |
|
level = (byte)(levState.runLevel+1); |
|
for (k = start0-1; k >= levState.startON; k--) { |
|
if (levels[k] > level) { |
|
levels[k] -= 2; |
|
} |
|
} |
|
break; |
|
|
|
default: |
|
throw new IllegalStateException("Internal ICU error in processPropertySeq"); |
|
} |
|
} |
|
if ((addLevel) != 0 || (start < start0)) { |
|
level = (byte)(levState.runLevel + addLevel); |
|
if (start >= levState.runStart) { |
|
for (k = start; k < limit; k++) { |
|
levels[k] = level; |
|
} |
|
} else { |
|
setLevelsOutsideIsolates(start, limit, level); |
|
} |
|
} |
|
} |
|
|
|
private void resolveImplicitLevels(int start, int limit, short sor, short eor) |
|
{ |
|
byte dirProp; |
|
LevState levState = new LevState(); |
|
int i, start1, start2; |
|
short oldStateImp, stateImp, actionImp; |
|
short gprop, resProp, cell; |
|
boolean inverseRTL; |
|
short nextStrongProp = R; |
|
int nextStrongPos = -1; |
|
|
|
/* check for RTL inverse Bidi mode */ |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
inverseRTL=((start<lastArabicPos) && ((GetParaLevelAt(start) & 1)>0) && |
|
(reorderingMode == REORDER_INVERSE_LIKE_DIRECT || |
|
reorderingMode == REORDER_INVERSE_FOR_NUMBERS_SPECIAL)); |
|
|
|
levState.startL2EN = -1; |
|
levState.lastStrongRTL = -1; |
|
levState.runStart = start; |
|
levState.runLevel = levels[start]; |
|
levState.impTab = impTabPair.imptab[levState.runLevel & 1]; |
|
levState.impAct = impTabPair.impact[levState.runLevel & 1]; |
|
|
|
|
|
|
|
when it was interrupted by an isolate sequence. */ |
|
if (dirProps[start] == PDI) { |
|
levState.startON = isolates[isolateCount].startON; |
|
start1 = isolates[isolateCount].start1; |
|
stateImp = isolates[isolateCount].stateImp; |
|
levState.state = isolates[isolateCount].state; |
|
isolateCount--; |
|
} else { |
|
levState.startON = -1; |
|
start1 = start; |
|
if (dirProps[start] == NSM) |
|
stateImp = (short)(1 + sor); |
|
else |
|
stateImp = 0; |
|
levState.state = 0; |
|
processPropertySeq(levState, sor, start, start); |
|
} |
|
start2 = start; /* to make the Java compiler happy */ |
|
|
|
for (i = start; i <= limit; i++) { |
|
if (i >= limit) { |
|
int k; |
|
for (k = limit - 1; |
|
k > start && |
|
(DirPropFlag(dirProps[k]) & MASK_BN_EXPLICIT) != 0; |
|
k--); |
|
dirProp = dirProps[k]; |
|
if (dirProp == LRI || dirProp == RLI) |
|
break; |
|
gprop = eor; |
|
} else { |
|
byte prop, prop1; |
|
prop = dirProps[i]; |
|
if (prop == B) |
|
isolateCount = -1; |
|
if (inverseRTL) { |
|
if (prop == AL) { |
|
|
|
prop = R; |
|
} else if (prop == EN) { |
|
if (nextStrongPos <= i) { |
|
|
|
int j; |
|
nextStrongProp = R; |
|
nextStrongPos = limit; |
|
for (j = i+1; j < limit; j++) { |
|
prop1 = dirProps[j]; |
|
if (prop1 == L || prop1 == R || prop1 == AL) { |
|
nextStrongProp = prop1; |
|
nextStrongPos = j; |
|
break; |
|
} |
|
} |
|
} |
|
if (nextStrongProp == AL) { |
|
prop = AN; |
|
} |
|
} |
|
} |
|
gprop = groupProp[prop]; |
|
} |
|
oldStateImp = stateImp; |
|
cell = impTabProps[oldStateImp][gprop]; |
|
stateImp = GetStateProps(cell); |
|
actionImp = GetActionProps(cell); |
|
if ((i == limit) && (actionImp == 0)) { |
|
|
|
actionImp = 1; /* process the last sequence */ |
|
} |
|
if (actionImp != 0) { |
|
resProp = impTabProps[oldStateImp][IMPTABPROPS_RES]; |
|
switch (actionImp) { |
|
case 1: |
|
processPropertySeq(levState, resProp, start1, i); |
|
start1 = i; |
|
break; |
|
case 2: |
|
start2 = i; |
|
break; |
|
case 3: |
|
processPropertySeq(levState, resProp, start1, start2); |
|
processPropertySeq(levState, _ON, start2, i); |
|
start1 = i; |
|
break; |
|
case 4: |
|
processPropertySeq(levState, resProp, start1, start2); |
|
start1 = start2; |
|
start2 = i; |
|
break; |
|
default: |
|
throw new IllegalStateException("Internal ICU error in resolveImplicitLevels"); |
|
} |
|
} |
|
} |
|
|
|
|
|
for (i = limit - 1; |
|
i > start && |
|
(DirPropFlag(dirProps[i]) & MASK_BN_EXPLICIT) != 0; |
|
i--); |
|
dirProp = dirProps[i]; |
|
if ((dirProp == LRI || dirProp == RLI) && limit < length) { |
|
isolateCount++; |
|
if (isolates[isolateCount] == null) |
|
isolates[isolateCount] = new Isolate(); |
|
isolates[isolateCount].stateImp = stateImp; |
|
isolates[isolateCount].state = levState.state; |
|
isolates[isolateCount].start1 = start1; |
|
isolates[isolateCount].startON = levState.startON; |
|
} |
|
else |
|
processPropertySeq(levState, eor, limit, limit); |
|
} |
|
|
|
/* perform (L1) and (X9) ---------------------------------------------------- */ |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void adjustWSLevels() { |
|
int i; |
|
|
|
if ((flags & MASK_WS) != 0) { |
|
int flag; |
|
i = trailingWSStart; |
|
while (i > 0) { |
|
|
|
while (i > 0 && ((flag = DirPropFlag(dirProps[--i])) & MASK_WS) != 0) { |
|
if (orderParagraphsLTR && (flag & DirPropFlag(B)) != 0) { |
|
levels[i] = 0; |
|
} else { |
|
levels[i] = GetParaLevelAt(i); |
|
} |
|
} |
|
|
|
/* reset BN to the next character's paraLevel until B/S, which restarts above loop */ |
|
|
|
while (i > 0) { |
|
flag = DirPropFlag(dirProps[--i]); |
|
if ((flag & MASK_BN_EXPLICIT) != 0) { |
|
levels[i] = levels[i + 1]; |
|
} else if (orderParagraphsLTR && (flag & DirPropFlag(B)) != 0) { |
|
levels[i] = 0; |
|
break; |
|
} else if ((flag & MASK_B_S) != 0){ |
|
levels[i] = GetParaLevelAt(i); |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
private void setParaSuccess() { |
|
paraBidi = this; /* mark successful setPara */ |
|
} |
|
|
|
private int Bidi_Min(int x, int y) { |
|
return x < y ? x : y; |
|
} |
|
|
|
private int Bidi_Abs(int x) { |
|
return x >= 0 ? x : -x; |
|
} |
|
|
|
void setParaRunsOnly(char[] parmText, byte parmParaLevel) { |
|
int[] visualMap; |
|
String visualText; |
|
int saveLength, saveTrailingWSStart; |
|
byte[] saveLevels; |
|
byte saveDirection; |
|
int i, j, visualStart, logicalStart, |
|
oldRunCount, runLength, addedRuns, insertRemove, |
|
start, limit, step, indexOddBit, logicalPos, |
|
index, index1; |
|
int saveOptions; |
|
|
|
reorderingMode = REORDER_DEFAULT; |
|
int parmLength = parmText.length; |
|
if (parmLength == 0) { |
|
setPara(parmText, parmParaLevel, null); |
|
reorderingMode = REORDER_RUNS_ONLY; |
|
return; |
|
} |
|
|
|
saveOptions = reorderingOptions; |
|
if ((saveOptions & OPTION_INSERT_MARKS) > 0) { |
|
reorderingOptions &= ~OPTION_INSERT_MARKS; |
|
reorderingOptions |= OPTION_REMOVE_CONTROLS; |
|
} |
|
parmParaLevel &= 1; |
|
setPara(parmText, parmParaLevel, null); |
|
|
|
|
|
*/ |
|
saveLevels = new byte[this.length]; |
|
System.arraycopy(getLevels(), 0, saveLevels, 0, this.length); |
|
saveTrailingWSStart = trailingWSStart; |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
visualText = writeReordered(DO_MIRRORING); |
|
visualMap = getVisualMap(); |
|
this.reorderingOptions = saveOptions; |
|
saveLength = this.length; |
|
saveDirection=this.direction; |
|
|
|
this.reorderingMode = REORDER_INVERSE_LIKE_DIRECT; |
|
parmParaLevel ^= 1; |
|
setPara(visualText, parmParaLevel, null); |
|
BidiLine.getRuns(this); |
|
|
|
addedRuns = 0; |
|
oldRunCount = this.runCount; |
|
visualStart = 0; |
|
for (i = 0; i < oldRunCount; i++, visualStart += runLength) { |
|
runLength = runs[i].limit - visualStart; |
|
if (runLength < 2) { |
|
continue; |
|
} |
|
logicalStart = runs[i].start; |
|
for (j = logicalStart+1; j < logicalStart+runLength; j++) { |
|
index = visualMap[j]; |
|
index1 = visualMap[j-1]; |
|
if ((Bidi_Abs(index-index1)!=1) || (saveLevels[index]!=saveLevels[index1])) { |
|
addedRuns++; |
|
} |
|
} |
|
} |
|
if (addedRuns > 0) { |
|
getRunsMemory(oldRunCount + addedRuns); |
|
if (runCount == 1) { |
|
|
|
runsMemory[0] = runs[0]; |
|
} else { |
|
System.arraycopy(runs, 0, runsMemory, 0, runCount); |
|
} |
|
runs = runsMemory; |
|
runCount += addedRuns; |
|
for (i = oldRunCount; i < runCount; i++) { |
|
if (runs[i] == null) { |
|
runs[i] = new BidiRun(0, 0, (byte)0); |
|
} |
|
} |
|
} |
|
|
|
int newI; |
|
for (i = oldRunCount-1; i >= 0; i--) { |
|
newI = i + addedRuns; |
|
runLength = i==0 ? runs[0].limit : |
|
runs[i].limit - runs[i-1].limit; |
|
logicalStart = runs[i].start; |
|
indexOddBit = runs[i].level & 1; |
|
if (runLength < 2) { |
|
if (addedRuns > 0) { |
|
runs[newI].copyFrom(runs[i]); |
|
} |
|
logicalPos = visualMap[logicalStart]; |
|
runs[newI].start = logicalPos; |
|
runs[newI].level = (byte)(saveLevels[logicalPos] ^ indexOddBit); |
|
continue; |
|
} |
|
if (indexOddBit > 0) { |
|
start = logicalStart; |
|
limit = logicalStart + runLength - 1; |
|
step = 1; |
|
} else { |
|
start = logicalStart + runLength - 1; |
|
limit = logicalStart; |
|
step = -1; |
|
} |
|
for (j = start; j != limit; j += step) { |
|
index = visualMap[j]; |
|
index1 = visualMap[j+step]; |
|
if ((Bidi_Abs(index-index1)!=1) || (saveLevels[index]!=saveLevels[index1])) { |
|
logicalPos = Bidi_Min(visualMap[start], index); |
|
runs[newI].start = logicalPos; |
|
runs[newI].level = (byte)(saveLevels[logicalPos] ^ indexOddBit); |
|
runs[newI].limit = runs[i].limit; |
|
runs[i].limit -= Bidi_Abs(j - start) + 1; |
|
insertRemove = runs[i].insertRemove & (LRM_AFTER|RLM_AFTER); |
|
runs[newI].insertRemove = insertRemove; |
|
runs[i].insertRemove &= ~insertRemove; |
|
start = j + step; |
|
addedRuns--; |
|
newI--; |
|
} |
|
} |
|
if (addedRuns > 0) { |
|
runs[newI].copyFrom(runs[i]); |
|
} |
|
logicalPos = Bidi_Min(visualMap[start], visualMap[limit]); |
|
runs[newI].start = logicalPos; |
|
runs[newI].level = (byte)(saveLevels[logicalPos] ^ indexOddBit); |
|
} |
|
|
|
cleanup1: |
|
|
|
this.paraLevel ^= 1; |
|
cleanup2: |
|
|
|
this.text = parmText; |
|
this.length = saveLength; |
|
this.originalLength = parmLength; |
|
this.direction=saveDirection; |
|
this.levels = saveLevels; |
|
this.trailingWSStart = saveTrailingWSStart; |
|
if (runCount > 1) { |
|
this.direction = MIXED; |
|
} |
|
cleanup3: |
|
this.reorderingMode = REORDER_RUNS_ONLY; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
void setPara(String text, byte paraLevel, byte[] embeddingLevels) |
|
{ |
|
if (text == null) { |
|
setPara(new char[0], paraLevel, embeddingLevels); |
|
} else { |
|
setPara(text.toCharArray(), paraLevel, embeddingLevels); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
void setPara(char[] chars, byte paraLevel, byte[] embeddingLevels) |
|
{ |
|
|
|
if (paraLevel < LEVEL_DEFAULT_LTR) { |
|
verifyRange(paraLevel, 0, MAX_EXPLICIT_LEVEL + 1); |
|
} |
|
if (chars == null) { |
|
chars = new char[0]; |
|
} |
|
|
|
|
|
if (reorderingMode == REORDER_RUNS_ONLY) { |
|
setParaRunsOnly(chars, paraLevel); |
|
return; |
|
} |
|
|
|
|
|
this.paraBidi = null; |
|
this.text = chars; |
|
this.length = this.originalLength = this.resultLength = text.length; |
|
this.paraLevel = paraLevel; |
|
this.direction = (byte)(paraLevel & 1); |
|
this.paraCount = 1; |
|
|
|
|
|
|
|
*/ |
|
dirProps = new byte[0]; |
|
levels = new byte[0]; |
|
runs = new BidiRun[0]; |
|
isGoodLogicalToVisualRunsMap = false; |
|
insertPoints.size = 0; |
|
insertPoints.confirmed = 0; /* clean up from last call */ |
|
|
|
|
|
|
|
*/ |
|
defaultParaLevel = IsDefaultLevel(paraLevel) ? paraLevel : 0; |
|
|
|
if (length == 0) { |
|
|
|
|
|
|
|
|
|
*/ |
|
if (IsDefaultLevel(paraLevel)) { |
|
this.paraLevel &= 1; |
|
defaultParaLevel = 0; |
|
} |
|
flags = DirPropFlagLR(paraLevel); |
|
runCount = 0; |
|
paraCount = 0; |
|
setParaSuccess(); |
|
return; |
|
} |
|
|
|
runCount = -1; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
getDirPropsMemory(length); |
|
dirProps = dirPropsMemory; |
|
getDirProps(); |
|
|
|
trailingWSStart = length; /* the levels[] will reflect the WS run */ |
|
|
|
|
|
if (embeddingLevels == null) { |
|
|
|
getLevelsMemory(length); |
|
levels = levelsMemory; |
|
direction = resolveExplicitLevels(); |
|
} else { |
|
|
|
levels = embeddingLevels; |
|
direction = checkExplicitLevels(); |
|
} |
|
|
|
|
|
if (isolateCount > 0) { |
|
if (isolates == null || isolates.length < isolateCount) |
|
isolates = new Isolate[isolateCount + 3]; /* keep some reserve */ |
|
} |
|
isolateCount = -1; /* current isolates stack entry == none */ |
|
|
|
|
|
|
|
|
|
*/ |
|
switch (direction) { |
|
case LTR: |
|
|
|
trailingWSStart = 0; |
|
break; |
|
case RTL: |
|
|
|
trailingWSStart = 0; |
|
break; |
|
default: |
|
|
|
|
|
*/ |
|
switch(reorderingMode) { |
|
case REORDER_DEFAULT: |
|
this.impTabPair = impTab_DEFAULT; |
|
break; |
|
case REORDER_NUMBERS_SPECIAL: |
|
this.impTabPair = impTab_NUMBERS_SPECIAL; |
|
break; |
|
case REORDER_GROUP_NUMBERS_WITH_R: |
|
this.impTabPair = impTab_GROUP_NUMBERS_WITH_R; |
|
break; |
|
case REORDER_RUNS_ONLY: |
|
|
|
throw new InternalError("Internal ICU error in setPara"); |
|
|
|
case REORDER_INVERSE_NUMBERS_AS_L: |
|
this.impTabPair = impTab_INVERSE_NUMBERS_AS_L; |
|
break; |
|
case REORDER_INVERSE_LIKE_DIRECT: |
|
if ((reorderingOptions & OPTION_INSERT_MARKS) != 0) { |
|
this.impTabPair = impTab_INVERSE_LIKE_DIRECT_WITH_MARKS; |
|
} else { |
|
this.impTabPair = impTab_INVERSE_LIKE_DIRECT; |
|
} |
|
break; |
|
case REORDER_INVERSE_FOR_NUMBERS_SPECIAL: |
|
if ((reorderingOptions & OPTION_INSERT_MARKS) != 0) { |
|
this.impTabPair = impTab_INVERSE_FOR_NUMBERS_SPECIAL_WITH_MARKS; |
|
} else { |
|
this.impTabPair = impTab_INVERSE_FOR_NUMBERS_SPECIAL; |
|
} |
|
break; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
if (embeddingLevels == null && paraCount <= 1 && |
|
(flags & DirPropFlagMultiRuns) == 0) { |
|
resolveImplicitLevels(0, length, |
|
GetLRFromLevel(GetParaLevelAt(0)), |
|
GetLRFromLevel(GetParaLevelAt(length - 1))); |
|
} else { |
|
|
|
int start, limit = 0; |
|
byte level, nextLevel; |
|
short sor, eor; |
|
|
|
|
|
level = GetParaLevelAt(0); |
|
nextLevel = levels[0]; |
|
if (level < nextLevel) { |
|
eor = GetLRFromLevel(nextLevel); |
|
} else { |
|
eor = GetLRFromLevel(level); |
|
} |
|
|
|
do { |
|
/* determine start and limit of the run (end points just behind the run) */ |
|
|
|
|
|
start = limit; |
|
level = nextLevel; |
|
if ((start > 0) && (dirProps[start - 1] == B)) { |
|
|
|
sor = GetLRFromLevel(GetParaLevelAt(start)); |
|
} else { |
|
sor = eor; |
|
} |
|
|
|
|
|
while ((++limit < length) && |
|
((levels[limit] == level) || |
|
((DirPropFlag(dirProps[limit]) & MASK_BN_EXPLICIT) != 0))) {} |
|
|
|
|
|
if (limit < length) { |
|
nextLevel = levels[limit]; |
|
} else { |
|
nextLevel = GetParaLevelAt(length - 1); |
|
} |
|
|
|
|
|
if (NoOverride(level) < NoOverride(nextLevel)) { |
|
eor = GetLRFromLevel(nextLevel); |
|
} else { |
|
eor = GetLRFromLevel(level); |
|
} |
|
|
|
|
|
are no implicit types to be resolved */ |
|
if ((level & LEVEL_OVERRIDE) == 0) { |
|
resolveImplicitLevels(start, limit, sor, eor); |
|
} else { |
|
|
|
do { |
|
levels[start++] &= ~LEVEL_OVERRIDE; |
|
} while (start < limit); |
|
} |
|
} while (limit < length); |
|
} |
|
|
|
|
|
adjustWSLevels(); |
|
|
|
break; |
|
} |
|
|
|
|
|
|
|
*/ |
|
if ((defaultParaLevel > 0) && |
|
((reorderingOptions & OPTION_INSERT_MARKS) != 0) && |
|
((reorderingMode == REORDER_INVERSE_LIKE_DIRECT) || |
|
(reorderingMode == REORDER_INVERSE_FOR_NUMBERS_SPECIAL))) { |
|
int start, last; |
|
byte level; |
|
byte dirProp; |
|
for (int i = 0; i < paraCount; i++) { |
|
last = paras_limit[i] - 1; |
|
level = paras_level[i]; |
|
if (level == 0) |
|
continue; |
|
start = i == 0 ? 0 : paras_limit[i - 1]; |
|
for (int j = last; j >= start; j--) { |
|
dirProp = dirProps[j]; |
|
if (dirProp == L) { |
|
if (j < last) { |
|
while (dirProps[last] == B) { |
|
last--; |
|
} |
|
} |
|
addPoint(last, RLM_BEFORE); |
|
break; |
|
} |
|
if ((DirPropFlag(dirProp) & MASK_R_AL) != 0) { |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if ((reorderingOptions & OPTION_REMOVE_CONTROLS) != 0) { |
|
resultLength -= controlCount; |
|
} else { |
|
resultLength += insertPoints.size; |
|
} |
|
setParaSuccess(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public void setPara(AttributedCharacterIterator paragraph) |
|
{ |
|
byte paraLvl; |
|
char ch = paragraph.first(); |
|
Boolean runDirection = |
|
(Boolean) paragraph.getAttribute(TextAttributeConstants.RUN_DIRECTION); |
|
Object shaper = paragraph.getAttribute(TextAttributeConstants.NUMERIC_SHAPING); |
|
|
|
if (runDirection == null) { |
|
paraLvl = LEVEL_DEFAULT_LTR; |
|
} else { |
|
paraLvl = (runDirection.equals(TextAttributeConstants.RUN_DIRECTION_LTR)) ? |
|
LTR : RTL; |
|
} |
|
|
|
byte[] lvls = null; |
|
int len = paragraph.getEndIndex() - paragraph.getBeginIndex(); |
|
byte[] embeddingLevels = new byte[len]; |
|
char[] txt = new char[len]; |
|
int i = 0; |
|
while (ch != AttributedCharacterIterator.DONE) { |
|
txt[i] = ch; |
|
Integer embedding = |
|
(Integer) paragraph.getAttribute(TextAttributeConstants.BIDI_EMBEDDING); |
|
if (embedding != null) { |
|
byte level = embedding.byteValue(); |
|
if (level == 0) { |
|
/* no-op */ |
|
} else if (level < 0) { |
|
lvls = embeddingLevels; |
|
embeddingLevels[i] = (byte)((0 - level) | LEVEL_OVERRIDE); |
|
} else { |
|
lvls = embeddingLevels; |
|
embeddingLevels[i] = level; |
|
} |
|
} |
|
ch = paragraph.next(); |
|
++i; |
|
} |
|
|
|
if (shaper != null) { |
|
NumericShapings.shape(shaper, txt, 0, len); |
|
} |
|
setPara(txt, paraLvl, lvls); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public void orderParagraphsLTR(boolean ordarParaLTR) { |
|
orderParagraphsLTR = ordarParaLTR; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public byte getDirection() |
|
{ |
|
verifyValidParaOrLine(); |
|
return direction; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public int getLength() |
|
{ |
|
verifyValidParaOrLine(); |
|
return originalLength; |
|
} |
|
|
|
/* paragraphs API methods ------------------------------------------------- */ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public byte getParaLevel() |
|
{ |
|
verifyValidParaOrLine(); |
|
return paraLevel; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public int getCustomizedClass(int c) { |
|
int dir; |
|
|
|
dir = bdp.getClass(c); |
|
if (dir >= CHAR_DIRECTION_COUNT) |
|
dir = ON; |
|
return dir; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Bidi setLine(Bidi bidi, BidiBase bidiBase, Bidi newBidi, BidiBase newBidiBase, int start, int limit) |
|
{ |
|
verifyValidPara(); |
|
verifyRange(start, 0, limit); |
|
verifyRange(limit, 0, length+1); |
|
|
|
return BidiLine.setLine(this, newBidi, newBidiBase, start, limit); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public byte getLevelAt(int charIndex) |
|
{ |
|
|
|
if (charIndex < 0 || charIndex >= length) { |
|
return (byte)getBaseLevel(); |
|
} |
|
|
|
verifyValidParaOrLine(); |
|
verifyRange(charIndex, 0, length); |
|
return BidiLine.getLevelAt(this, charIndex); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
byte[] getLevels() |
|
{ |
|
verifyValidParaOrLine(); |
|
if (length <= 0) { |
|
return new byte[0]; |
|
} |
|
return BidiLine.getLevels(this); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public int countRuns() |
|
{ |
|
verifyValidParaOrLine(); |
|
BidiLine.getRuns(this); |
|
return runCount; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
BidiRun getVisualRun(int runIndex) |
|
{ |
|
verifyValidParaOrLine(); |
|
BidiLine.getRuns(this); |
|
verifyRange(runIndex, 0, runCount); |
|
return BidiLine.getVisualRun(this, runIndex); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private int[] getVisualMap() |
|
{ |
|
|
|
countRuns(); |
|
if (resultLength <= 0) { |
|
return new int[0]; |
|
} |
|
return BidiLine.getVisualMap(this); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static int[] reorderVisual(byte[] levels) |
|
{ |
|
return BidiLine.reorderVisual(levels); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static final int DIRECTION_DEFAULT_RIGHT_TO_LEFT = LEVEL_DEFAULT_RTL; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public BidiBase(char[] text, |
|
int textStart, |
|
byte[] embeddings, |
|
int embStart, |
|
int paragraphLength, |
|
int flags) |
|
{ |
|
this(0, 0); |
|
byte paraLvl; |
|
switch (flags) { |
|
case Bidi.DIRECTION_LEFT_TO_RIGHT: |
|
default: |
|
paraLvl = LTR; |
|
break; |
|
case Bidi.DIRECTION_RIGHT_TO_LEFT: |
|
paraLvl = RTL; |
|
break; |
|
case Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT: |
|
paraLvl = LEVEL_DEFAULT_LTR; |
|
break; |
|
case Bidi.DIRECTION_DEFAULT_RIGHT_TO_LEFT: |
|
paraLvl = LEVEL_DEFAULT_RTL; |
|
break; |
|
} |
|
byte[] paraEmbeddings; |
|
if (embeddings == null) { |
|
paraEmbeddings = null; |
|
} else { |
|
paraEmbeddings = new byte[paragraphLength]; |
|
byte lev; |
|
for (int i = 0; i < paragraphLength; i++) { |
|
lev = embeddings[i + embStart]; |
|
if (lev < 0) { |
|
lev = (byte)((- lev) | LEVEL_OVERRIDE); |
|
} else if (lev == 0) { |
|
lev = paraLvl; |
|
if (paraLvl > MAX_EXPLICIT_LEVEL) { |
|
lev &= 1; |
|
} |
|
} |
|
paraEmbeddings[i] = lev; |
|
} |
|
} |
|
|
|
char[] paraText = new char[paragraphLength]; |
|
System.arraycopy(text, textStart, paraText, 0, paragraphLength); |
|
setPara(paraText, paraLvl, paraEmbeddings); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public boolean isMixed() |
|
{ |
|
return (!isLeftToRight() && !isRightToLeft()); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public boolean isLeftToRight() |
|
{ |
|
return (getDirection() == LTR && (paraLevel & 1) == 0); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public boolean isRightToLeft() |
|
{ |
|
return (getDirection() == RTL && (paraLevel & 1) == 1); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public boolean baseIsLeftToRight() |
|
{ |
|
return (getParaLevel() == LTR); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public int getBaseLevel() |
|
{ |
|
return getParaLevel(); |
|
} |
|
|
|
|
|
|
|
*/ |
|
void getLogicalToVisualRunsMap() |
|
{ |
|
if (isGoodLogicalToVisualRunsMap) { |
|
return; |
|
} |
|
int count = countRuns(); |
|
if ((logicalToVisualRunsMap == null) || |
|
(logicalToVisualRunsMap.length < count)) { |
|
logicalToVisualRunsMap = new int[count]; |
|
} |
|
int i; |
|
long[] keys = new long[count]; |
|
for (i = 0; i < count; i++) { |
|
keys[i] = ((long)(runs[i].start)<<32) + i; |
|
} |
|
Arrays.sort(keys); |
|
for (i = 0; i < count; i++) { |
|
logicalToVisualRunsMap[i] = (int)(keys[i] & 0x00000000FFFFFFFF); |
|
} |
|
isGoodLogicalToVisualRunsMap = true; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public int getRunLevel(int run) |
|
{ |
|
verifyValidParaOrLine(); |
|
BidiLine.getRuns(this); |
|
|
|
|
|
if (run < 0 || run >= runCount) { |
|
return getParaLevel(); |
|
} |
|
|
|
getLogicalToVisualRunsMap(); |
|
return runs[logicalToVisualRunsMap[run]].level; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public int getRunStart(int run) |
|
{ |
|
verifyValidParaOrLine(); |
|
BidiLine.getRuns(this); |
|
|
|
|
|
if (runCount == 1) { |
|
return 0; |
|
} else if (run == runCount) { |
|
return length; |
|
} |
|
|
|
getLogicalToVisualRunsMap(); |
|
return runs[logicalToVisualRunsMap[run]].start; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public int getRunLimit(int run) |
|
{ |
|
verifyValidParaOrLine(); |
|
BidiLine.getRuns(this); |
|
|
|
|
|
if (runCount == 1) { |
|
return length; |
|
} |
|
|
|
getLogicalToVisualRunsMap(); |
|
int idx = logicalToVisualRunsMap[run]; |
|
int len = idx == 0 ? runs[idx].limit : |
|
runs[idx].limit - runs[idx-1].limit; |
|
return runs[idx].start + len; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static boolean requiresBidi(char[] text, |
|
int start, |
|
int limit) |
|
{ |
|
final int RTLMask = (1 << R | |
|
1 << AL | |
|
1 << RLE | |
|
1 << RLO | |
|
1 << AN); |
|
|
|
if (0 > start || start > limit || limit > text.length) { |
|
throw new IllegalArgumentException("Value start " + start + |
|
" is out of range 0 to " + limit); |
|
} |
|
|
|
for (int i = start; i < limit; ++i) { |
|
if (Character.isHighSurrogate(text[i]) && i < (limit-1) && |
|
Character.isLowSurrogate(text[i+1])) { |
|
if (((1 << UCharacter.getDirection(Character.codePointAt(text, i))) & RTLMask) != 0) { |
|
return true; |
|
} |
|
} else if (((1 << UCharacter.getDirection(text[i])) & RTLMask) != 0) { |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static void reorderVisually(byte[] levels, |
|
int levelStart, |
|
Object[] objects, |
|
int objectStart, |
|
int count) |
|
{ |
|
|
|
if (0 > levelStart || levels.length <= levelStart) { |
|
throw new IllegalArgumentException("Value levelStart " + |
|
levelStart + " is out of range 0 to " + |
|
(levels.length-1)); |
|
} |
|
if (0 > objectStart || objects.length <= objectStart) { |
|
throw new IllegalArgumentException("Value objectStart " + |
|
levelStart + " is out of range 0 to " + |
|
(objects.length-1)); |
|
} |
|
if (0 > count || objects.length < (objectStart+count)) { |
|
throw new IllegalArgumentException("Value count " + |
|
levelStart + " is out of range 0 to " + |
|
(objects.length - objectStart)); |
|
} |
|
|
|
byte[] reorderLevels = new byte[count]; |
|
System.arraycopy(levels, levelStart, reorderLevels, 0, count); |
|
int[] indexMap = reorderVisual(reorderLevels); |
|
Object[] temp = new Object[count]; |
|
System.arraycopy(objects, objectStart, temp, 0, count); |
|
for (int i = 0; i < count; ++i) { |
|
objects[objectStart + i] = temp[indexMap[i]]; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public String writeReordered(int options) |
|
{ |
|
verifyValidParaOrLine(); |
|
if (length == 0) { |
|
|
|
return ""; |
|
} |
|
return BidiWriter.writeReordered(this, options); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public String toString() { |
|
StringBuilder buf = new StringBuilder(getClass().getName()); |
|
|
|
buf.append("[dir: "); |
|
buf.append(direction); |
|
buf.append(" baselevel: "); |
|
buf.append(paraLevel); |
|
buf.append(" length: "); |
|
buf.append(length); |
|
buf.append(" runs: "); |
|
if (levels == null) { |
|
buf.append("none"); |
|
} else { |
|
buf.append('['); |
|
buf.append(levels[0]); |
|
for (int i = 1; i < levels.length; i++) { |
|
buf.append(' '); |
|
buf.append(levels[i]); |
|
} |
|
buf.append(']'); |
|
} |
|
buf.append(" text: [0x"); |
|
buf.append(Integer.toHexString(text[0])); |
|
for (int i = 1; i < text.length; i++) { |
|
buf.append(" 0x"); |
|
buf.append(Integer.toHexString(text[i])); |
|
} |
|
buf.append("]]"); |
|
|
|
return buf.toString(); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static class TextAttributeConstants { |
|
|
|
static { |
|
try { |
|
Class.forName("java.awt.font.TextAttribute", true, null); |
|
} catch (ClassNotFoundException e) {} |
|
} |
|
static final JavaAWTFontAccess jafa = SharedSecrets.getJavaAWTFontAccess(); |
|
|
|
|
|
|
|
|
|
*/ |
|
static final AttributedCharacterIterator.Attribute RUN_DIRECTION = |
|
getTextAttribute("RUN_DIRECTION"); |
|
static final AttributedCharacterIterator.Attribute NUMERIC_SHAPING = |
|
getTextAttribute("NUMERIC_SHAPING"); |
|
static final AttributedCharacterIterator.Attribute BIDI_EMBEDDING = |
|
getTextAttribute("BIDI_EMBEDDING"); |
|
|
|
|
|
|
|
*/ |
|
static final Boolean RUN_DIRECTION_LTR = (jafa == null) ? |
|
Boolean.FALSE : (Boolean)jafa.getTextAttributeConstant("RUN_DIRECTION_LTR"); |
|
|
|
@SuppressWarnings("serial") |
|
private static AttributedCharacterIterator.Attribute |
|
getTextAttribute(String name) |
|
{ |
|
if (jafa == null) { |
|
|
|
return new AttributedCharacterIterator.Attribute(name) { }; |
|
} else { |
|
return (AttributedCharacterIterator.Attribute)jafa.getTextAttributeConstant(name); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static class NumericShapings { |
|
|
|
static { |
|
try { |
|
Class.forName("java.awt.font.NumericShaper", true, null); |
|
} catch (ClassNotFoundException e) {} |
|
} |
|
static final JavaAWTFontAccess jafa = SharedSecrets.getJavaAWTFontAccess(); |
|
|
|
|
|
|
|
*/ |
|
static void shape(Object shaper, char[] text, int start, int count) { |
|
if (jafa != null) { |
|
jafa.shape(shaper, text, start, count); |
|
} |
|
} |
|
} |
|
|
|
} |