com.adobe.xfa.text.MappingManager Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
package com.adobe.xfa.text;
/**
* Manage complex character/glyph mappings during layout.
*
* This class is used during layout to generate glyph locations for
* wrapped lines. Note that the raw line glyph locations are generated
* by raw formatting, from information accumulated in the AFE run. A
* mapping manager is not required if all mappings are simple; that is,
* every character maps 1:1 to a glyph and there is no reordering.
*
*
* Higher level code uses a mapping manager instance in the following
* sequence of operations:
*
*
* -
* Call the analyze() method once, with the raw line.
*
* -
* Call the applyToWrappedLine() method once for each wrapped line
* generated from the original raw line.
*
*
*
* Once that sequence is finished, the mapping manager instance can be
* reused for another complete cycle.
*
*
* The presence of RTL text in a raw line implies complex mapping. AFE
* formatting occurs on the raw line, and is dependent on the BIDI run
* levels. These are determined by the mapping manager. Thus, if the
* raw line has any RTL text, analysis must occur before AFE formatting.
*
*
* If the line has no RTL text, it may or may not have complex mapping.
* This can be determined only after AFE formatting is complete. Thus,
* the caller can defer analysis (if there is no RTL text), but will
* have to check again after AFE formatting to determine whether
* analysis is now required.
*
*
* The bulk of the code in this class implements the Unicode BIDI
* Algorithm, UAX9.
*
* @exclude from published api.
*/
class MappingManager {
/**
* Resolve explicit direction control.
*
* The first stage of the BIDI algorithm is to check for runs in the
* text whose direction is controlled by markup. There are two
* constructs for representing such mark-up: HTML DIR attributes
* (represented at this point in TextAttr objects), and by the explicit
* Unicode direction control characters:
*
*
* -
* LRE - Left to right embedding
*
* -
* LRO - Left to right override
*
* -
* RLE - Right to left embedding
*
* -
* RLO - Right to left override
*
*
*
* A run introduced by one of the above characters is terminated by PDF
* (pop direction format). Runs may nest.
*
*
* This reusable class performs the initial population of the directions
* and levels arrays based on explicit direction control.
*
*/
private static class ExplicitRunProcessor {
private DispLine mLine;
private int mCharCount;
private int [] mDirections;
private byte[] mLevels;
private boolean mDefaultRTL;
private int mDefaultEmbedding;
private int mRunIndex;
private int mRunLimit;
/**
* Initialize the processor for processing a line's text. Upon return,
* the directions and levels arrays will be populated for further
* processing.
* @param line - Line to process.
* @param directions - Pre-created directions array. Must be large
* enough to hold one element for each character in the corresponding
* line.
* @param levels - Precreated levels array. Must be large enough to
* hold one element for each character in the corresponding line.
*/
void setup (DispLine line, int [] directions, byte [] levels) {
mLine = line;
mCharCount = mLine.getCharCount();
mDirections = directions;
mLevels = levels;
mDefaultRTL = mLine.isRTL();
mDefaultEmbedding = mDefaultRTL ? TextCharProp.BIDI_RLE : TextCharProp.BIDI_LRE;
mRunIndex = 0;
mRunLimit = 0;
byte defaultLevel = (byte) (mDefaultRTL ? 1 : 0);
for (int i = 0; i < mCharCount; i++) {
mDirections[i] = line.getBreakData (i);
mLevels[i] = defaultLevel;
}
}
/**
* Process the line's content. Using the parameters specified in the
* setup() method, process the content of the line to perform the
* initial population of the directions and levels arrays.
*/
void run () {
processExplicitRun (0, mDefaultEmbedding, mDefaultRTL ? 1 : 0, TextCharProp.BIDI_ON);
}
/**
* Recursive run processing.
*
* This method processes the text by running through the text from start
* to end, calling itself recursively each time a new run is introduced
* and returning when the corresponding end-of-run is encountered.
*
* @param index - Index to start processing at. This should be zero for
* the initial call from the "outside".
* @param level - The level to start processing out. This should be the
* paragraph level for the initial call from the "outside".
* @param override - Direction override, or neutral, to enforce. This
* should be TextCharProp.BIDI_ON (other neutral) the initial call from
* the "outside".
* @return Index to carry on from when a recursive call returns.
*/
private int processExplicitRun (int index, int dispRunDir, int level, int override) {
boolean atInitialDepth = index == 0;
// The loop normally iterates once for each character in the line.
// Calling recursively happens within the loop, causing a skip ahead at
// this level of recursion. If the end of the run that triggered this
// call is detected, the call returns. Otherwise, processing continues
// to the end of the text.
while (index < mLine.getCharCount()) {
// First check to see whether there is an AXTE direction change at this
// position. This could happen when we step into a new display run, but
// not at any other time.
if (index >= mRunLimit) {
mRunIndex++;
if (mRunIndex >= mLine.getRunCount()) {
mRunLimit = mLine.getCharCount();
} else {
DispRun run = mLine.getRun (mRunIndex);
mRunLimit = run.getMapIndex() + run.getMapLength();
TextAttr textAttr = run.getAttr();
if (textAttr != null) {
int newDispRunDir;
switch (textAttr.direction()) {
case TextAttr.DIRECTION_LTR:
newDispRunDir = TextCharProp.BIDI_LRE;
break;
case TextAttr.DIRECTION_RTL:
newDispRunDir = TextCharProp.BIDI_RLE;
break;
default:
newDispRunDir = mDefaultEmbedding;
break;
}
// AXTE runs do not nest. If there is a change in AXTE direction, it is
// either a change away from the paragraph direction, or a change back to
// it. Changing away is treated as a new embedded run (recursive call).
// Changing back is treated as the end of an embedded run (return).
if (newDispRunDir != dispRunDir) {
if (newDispRunDir == mDefaultEmbedding) {
return index; // not incremented; process this char in new run
}
int newLevel = (newDispRunDir == TextCharProp.BIDI_LRE)
? nextEvenLevel (level)
: nextOddLevel (level);
index = processExplicitRun (index, newDispRunDir, newLevel, override);
if (index >= mCharCount) {
return index;
}
}
}
}
}
// Now the current character can be processed. The code below examines
// it for one of the Unicode direction control characters that introduces
// an embedded run. If one is present, it triggers a recursive call. If
// the PDF character is encountered, it's time to return. Otherwise, the
// character has no effect on run level.
int charData = mLine.getBreakData (index);
int direction = TextCharProp.getBIDIClass (charData);
int newLevel = level;
int newOverride = override;
switch (direction) {
case TextCharProp.BIDI_LRE: // rule X3
newLevel = nextEvenLevel (level);
newOverride = TextCharProp.BIDI_ON;
break;
case TextCharProp.BIDI_LRO: // rule X5
newLevel = nextEvenLevel (level);
newOverride = TextCharProp.BIDI_L;
break;
case TextCharProp.BIDI_RLE: // rule X2
newLevel = nextOddLevel (level);
newOverride = TextCharProp.BIDI_ON;
break;
case TextCharProp.BIDI_RLO: // rule X4
newLevel = nextOddLevel (level);
newOverride = TextCharProp.BIDI_R;
break;
case TextCharProp.BIDI_PDF: // rule X7
if (! atInitialDepth) {
mLevels[index] = (byte) level;
mDirections[index] = resolveDirection (override, direction);
return index + 1; // continue after PDF
}
}
if (newLevel != level) {
if (newLevel > 61) { // maximum UAX9 level
newLevel = level;
newOverride = override;
}
}
mLevels[index] = (byte) newLevel;
mDirections[index] = resolveDirection (newOverride, direction);
if (newLevel == level) {
index++; // increment only if not pushing new level
} else {
index = processExplicitRun (index+1, dispRunDir, newLevel, newOverride);
if (index >= mCharCount) {
return index;
}
}
}
return index;
}
}
/**
* Base class for level run processing.
*
* A level run is a maximal set of consecutive characters at the same
* BIDI level. The entire text can be partitioned into a set of level
* runs.
*
*
* This is the base class for different kinds of processing on level
* runs. The caller creates an instance of a derived class and then
* calls the execute() method to perform (derived class) processing on
* each level run in the text.
*
*/
private abstract class LevelRunProcessor {
LevelRunProcessor () {
}
/**
* Invoke level run processing.
*/
void execute () {
// The loop iterates once for each level run. At the start of each
// iteration, variable i is positioned at the start of the run.
int i = 0;
while (i < mCharCount) {
int runStart = i;
int sorLevel = mLevels[i]; // sor is defined in UAX9
if ((i > 0) && (mLevels[i-1] > sorLevel)) {
sorLevel = mLevels[i-1];
}
// Now, scan from the next character until one is found at a different
// level (or end of text encountered).
i++;
int eorLevel = sorLevel; // eor is defined in UAX9
while (i < mCharCount) {
int nextLevel = mLevels[i];
if (nextLevel != eorLevel) {
if (nextLevel > eorLevel) {
eorLevel = nextLevel;
}
break;
}
i++;
}
int runLimit = i;
processRun (runStart, runLimit, sorLevel, eorLevel);
}
}
/**
* Process one character in the level run.
*
* Implemented by the derived class. Typically the implementation
* examines the character's direction code in the context of the
* previous and next characters' direction and may alter the current
* character's direction accordingly.
*
* @param index - Index of the character being processed.
* @param prevDirection - Direction of the preceding character.
* @param direction - Direction of this character.
* @param nextDirection - Direction of the following character.
* @return Overriding direction code to be used as the previous
* direction code next time. Alternatively, this can be -1, indicating
* that the (possibly altered) current character's direction code is to
* be used.
*/
abstract int processChar (int index, int prevDirection, int direction, int nextDirection);
void finishRun () {
}
final int resolveENDirection (int prevStrong) {
return (prevStrong == TextCharProp.BIDI_L) ? TextCharProp.BIDI_L : TextCharProp.BIDI_EN;
}
final int getDirection (int index) {
return mDirections[index];
}
final void setDirection (int index, int direction) {
// JavaPort TODO: What was this ported from - same code in both branches doesn't make sense.
// if (direction == 0) {
mDirections[index] = direction;
// }else {
// mDirections[index] = direction;
// }
}
final void setDirection (int start, int limit, int direction) {
if (start >= 0) { // rule W5
for (int i = start; i < limit; i = findNonExplicit (i+1, limit)) {
setDirection (i, direction);
}
}
}
private void processRun (int runStart, int runLimit, int sorLevel, int eorLevel) {
int index = findNonExplicit (runStart, runLimit);
if (index >= runLimit) {
return;
}
int direction = mDirections[index];
int prevDirection = getDirectionFromLevel (sorLevel);
while (index < runLimit) {
int nextIndex = findNonExplicit (index+1, runLimit);
int nextDirection = (nextIndex < runLimit) ? mDirections[nextIndex] : getDirectionFromLevel (eorLevel);
int prevOverride = processChar (index, prevDirection, direction, nextDirection);
prevDirection = (prevOverride == -1) ? mDirections[index] : prevOverride;
direction = nextDirection;
index = nextIndex;
}
finishRun();
}
}
/**
* Resolves weak direction types (UAX9, section 3.3.3).
*/
private class WeakTypeResolver extends LevelRunProcessor {
private int mPrevStrong;
private int mETRunStart;
WeakTypeResolver () {
}
int processChar (int index, int prevDirection, int direction, int nextDirection) {
// Note: UAX9 describes weak type resolution in terms of several
// iterations (with sub-iterations) over the text. It could conceivably
// be an order N**2 algorithm as described. This implementation allows
// the resolution to occur in a single pass of the text (with the
// occasional sub-iteration). Consequently, the UAX9 rules may not be
// immediately evident in a quick scan of the code.
int prevOverride = -1;
boolean preserveETRun = false;
switch (direction) {
case TextCharProp.BIDI_NSM: // rule W1
setDirection (index, prevDirection);
break;
case TextCharProp.BIDI_EN:
if (mPrevStrong == TextCharProp.BIDI_AL) {
setDirection (index, TextCharProp.BIDI_AN); // rule W2
} else {
int newDirection = resolveENDirection (mPrevStrong); // rule W7
setDirection (index, newDirection);
setDirection (mETRunStart, index, newDirection); // rule W5
}
prevOverride = TextCharProp.BIDI_EN; // in case changed to L or AN
break;
case TextCharProp.BIDI_AL: // rule W3
setDirection (index, TextCharProp.BIDI_R);
break;
case TextCharProp.BIDI_ES:
if ((prevDirection == TextCharProp.BIDI_EN)
&& (nextDirection == TextCharProp.BIDI_EN)) { // rule W4
setDirection (index, resolveENDirection (mPrevStrong));
} else {
setDirection (index, TextCharProp.BIDI_ON);
}
break;
case TextCharProp.BIDI_CS:
boolean usePrev = false;
if (prevDirection == nextDirection) {
switch (prevDirection) {
case TextCharProp.BIDI_AN:
case TextCharProp.BIDI_EN:
usePrev = true;
}
}
if (usePrev) {
setDirection (index, prevDirection); // rule W4
} else {
setDirection (index, TextCharProp.BIDI_ON); // rule W6
}
break;
case TextCharProp.BIDI_ET:
if (prevDirection == TextCharProp.BIDI_EN) {
setDirection (index, resolveENDirection (mPrevStrong)); // rule W5, W7
prevOverride = TextCharProp.BIDI_EN; // in case changed to L
} else {
preserveETRun = true;
if (mETRunStart < 0) {
mETRunStart = index; // for rule W5 later
}
}
break;
default:
if (isStrong (direction)) {
mPrevStrong = direction;
}
}
if (! preserveETRun) {
mETRunStart = -1;
}
return prevOverride;
}
}
private class NeutralTypeResolver extends LevelRunProcessor {
private int mRunDirection;
private int mPrevStrongDirection;
private int mNextStrongDirection;
private int mFirstNeutralIndex = -1;
private int mNextIndex;
private boolean mFirstTime = true;
NeutralTypeResolver () {
}
int processChar (int index, int prevDirection, int direction, int nextDirection) {
// As with weak type processing, the resolution of neutral types (UAX9,
// section 3.3.4) is collapsed into a single pass.
if (mFirstTime) {
mRunDirection = getDirectionFromLevel (getDirection (index));
mPrevStrongDirection = prevDirection;
mFirstTime = false;
}
int strongDir = TextCharProp.BIDI_ON;
switch (direction) { // rule N1
case TextCharProp.BIDI_AL:
case TextCharProp.BIDI_AN:
case TextCharProp.BIDI_EN:
case TextCharProp.BIDI_R:
strongDir = TextCharProp.BIDI_R;
break;
case TextCharProp.BIDI_L:
strongDir = TextCharProp.BIDI_L;
break;
case TextCharProp.BIDI_B:
case TextCharProp.BIDI_S:
case TextCharProp.BIDI_WS:
case TextCharProp.BIDI_ON:
if (mFirstNeutralIndex < 0) {
mFirstNeutralIndex = index;
}
break;
}
if (strongDir != TextCharProp.BIDI_ON) {
flushRun (index, strongDir);
mPrevStrongDirection = strongDir;
}
mNextStrongDirection = nextDirection;
mNextIndex = index + 1;
return -1;
}
void finishRun () {
flushRun (mNextIndex, mNextStrongDirection);
}
private final void flushRun (int limit, int direction) {
if (mFirstNeutralIndex >= 0) {
int neutralDir = (direction == mPrevStrongDirection)
? direction // rule N1
: mRunDirection; // rule N2
setDirection (mFirstNeutralIndex, limit, neutralDir);
mFirstNeutralIndex = -1;
}
}
}
private final ExplicitRunProcessor mExplicitRunProcessor = new ExplicitRunProcessor();
private DispLine mLine;
private int mCharCount;
private byte[] mLevels;
private int[] mDirections; // TODO: use char prop indexes and byte(?); cache in text context
/**
* Constructor.
*/
MappingManager () {
}
/**
* Analyze direction information in a raw line and build internal
* tables.
*
* This method is called once for each raw line, to collect all the
* necessary BIDI information. Method applyToWrappedLine() is then
* called once for each wrapped line generated from the raw line.
*
*
* Information is cached in this object by this call. This method must
* not be called on the same instance until all wrapped lines have been
* dealt with in calls to applyToWrappedLine(). Once that occurs, the
* mapping manager instance can be used for a new raw line.
*
* @param line - Raw line to analyze.
*/
void analyze (DispLine line) {
mLine = line;
mCharCount = line.getCharCount();
if (mCharCount == 0) {
return;
}
if (! mLine.hasBIDI()) {
return;
}
if ((mDirections == null) || (mDirections.length < mCharCount)) {
mDirections = new int[mCharCount];
mLevels = new byte[mCharCount];
}
// Perform the initial population of the BIDI levels, based on embedded
// Unicode direction control characters and AXTE direction changes.
mExplicitRunProcessor.setup (mLine, mDirections, mLevels);
mExplicitRunProcessor.run();
// Resolve weak and neutral types, respectively.
WeakTypeResolver weakResolver = new WeakTypeResolver();
weakResolver.execute();
NeutralTypeResolver neutralResolver = new NeutralTypeResolver();
neutralResolver.execute();
// At this point, direction codes should now be collapsed to the types
// AN, EN, R, L, or the Unicode direction control types (which are
// ignored). Based on those four key types, this loop adjusts the levels
// according to UAX9, section 3.3.5.
for (int i = 0; i < mCharCount; i++) {
int level = mLevels[i];
int direction = mDirections[i];
int defaultDirection = getDirectionFromLevel (level);
int add = 0;
switch (direction) {
case TextCharProp.BIDI_AN:
case TextCharProp.BIDI_EN:
if (defaultDirection == TextCharProp.BIDI_R) {
add = 1; // rule I2
} else {
add = 2; // rule I1
}
break;
case TextCharProp.BIDI_L:
if (defaultDirection == TextCharProp.BIDI_R) {
add = 1; // rule I2
}
break;
case TextCharProp.BIDI_R:
if (defaultDirection == TextCharProp.BIDI_L) {
add = 1; // rule I1
}
break;
}
mLevels[i] = (byte) (level + add);
}
}
/**
* Apply direction information to one wrapped line.
*
* Once raw line analysis is complete (see method analyze()), this
* method is called once for each wrapped line generated from the raw
* line. It must be called only once for each wrapped line, as the call
* does alter cached information pertaining to that line.
*
*
* This method is called after the wrapped line's character content has
* been created, but before its glyphs have. Upon return, this method
* will have populated the glyph locations for the line. The caller can
* use these locations to populate glyphs in the correct order.
*
* @param wrappedLine - Wrapped line to be altered with mapping
* information.
* @param afeRun - AFE run to update.
* @param start - Starting character index of the wrapped line in the
* originating raw line.
*/
void applyToWrappedLine (DispLineWrapped wrappedLine, AFERun afeRun, int start) {
int length = wrappedLine.getCharCount();
if (length <= 0) {
return;
}
int i;
int limit = start + length;
DispMap glyphLocMap = mLine.getGlyphLocMap();
int glyphLocCount = glyphLocMap.size();
if (glyphLocCount == 0) {
glyphLocMap = null;
}
int[] indexes = null;
if (mLine.hasBIDI()) {
// UAX9 rule L1 requires that certain spaces and related characters drop
// back to the default run level after line breaking. This call does
// that and also determines the maximum level present.
int maxLevel = resetTrailingSpaces (start, limit, afeRun); // TODO: shortcut if maxLevel==0?
// This is where run reordering occurs. The result is an array of
// _character_ indexes in rendering order.
indexes = reorderRuns (start, limit, maxLevel);
}
boolean[] visited = new boolean [length]; // TODO: cache this?
for (i = 0; i < length; i++) {
visited[i] = false;
}
// This loop creates the wrapped line's glyph locations, using the raw
// line's locations as input, but ordering them appropriately.
int glyphIndex = 0;
GlyphLoc glyphLoc = new GlyphLoc();
for (i = 0; i < length; i++) {
int rawIndex = (indexes == null) ? (i + start) : indexes[i];
int visitedIndex = rawIndex - start;
assert ((visitedIndex >= 0) && (visitedIndex < length));
if (! visited[visitedIndex]) {
// There are rare cases where a single character can generate multiple
// consecutive glyphs. The following code sets start and limit indexes
// for source glyph locations, corresponding to the current character.
// If necessary, work both backward and forward in the parent glyph
// location map to find the range of glyph locations that refer to this
// character.
int glyphLocIndex = rawIndex;
int glyphLocLimit = rawIndex + 1;
if (glyphLocMap != null) {
int foundIndex = glyphLocMap.findItem (rawIndex);
boolean charPresent = false;
if (glyphLocMap.isValidMapIndex (foundIndex)) {
DispMapItem testGlyphLoc = glyphLocMap.getItem (foundIndex);
int charStart = testGlyphLoc.getMapIndex();
int charLimit = charStart + testGlyphLoc.getMapLength();
if ((charStart <= rawIndex) && (rawIndex < charLimit)) {
charPresent = true;
}
}
if (charPresent) {
glyphLocIndex = foundIndex;
int referenceIndex = mLine.getGlyphLoc(foundIndex).getMapIndex();
int testIndex;
for (testIndex = foundIndex; testIndex > 0; ) {
testIndex--;
if (glyphLocMap.getItem(testIndex).getMapIndex() != referenceIndex) {
break;
}
glyphLocIndex = testIndex;
}
glyphLocLimit = foundIndex + 1;
for (testIndex = glyphLocLimit; testIndex < glyphLocCount; testIndex++) {
if (glyphLocMap.getItem(testIndex).getMapIndex() != referenceIndex) {
break;
}
glyphLocLimit = testIndex;
}
} else {
glyphLocLimit = glyphLocIndex; // deleted during AFE formatting
}
}
// Step through the range of glyph locations determined above (usually
// its length is one), creating a corresponding glyph location in the
// wrapped line for each one.
while (glyphLocIndex < glyphLocLimit) {
glyphLoc.copyFrom (mLine.getGlyphLoc (glyphLocIndex));
int wrappedIndex = glyphLoc.getMapIndex() - start;
assert (wrappedIndex >= 0);
for (int j = 0; j < glyphLoc.getMapLength(); j++) {
int index = wrappedIndex + j;
assert (index < length);
visited[index] = true;
}
glyphLoc.setGlyphIndex (glyphIndex);
wrappedLine.add (glyphLoc, wrappedIndex, glyphLoc.getMapLength());
glyphIndex++;
glyphLocIndex++;
}
}
}
}
int getLevel (int index) {
return mLevels[index];
}
/**
* Reset trailing space levels.
*
* Once the text has been broken into lines, the run levels of all
* trailing spaces are set to the paragraph level. This keeps those
* spaces at the end of the line (by paragraph direction), rather than
* appearing in the middle (if the line ends in text of the opposite
* direction). Spaces before tabs must be similarly adjusted.
*
* @param start - Index in the parent raw line designating the start of
* this wrapped line.
* @param linit - limit in the parent raw line designating the end of
* this wrapped line.
* @param afeRun - AFE run to update.
* @return Maximum BIDI level within this wrapped line.
*/
private int resetTrailingSpaces (int start, int limit, AFERun afeRun) {
int maxLevel = 0;
byte defaultLevel = (byte) getDefaultLevel();
boolean defaultWhitespaceLevels = true;
for (int i = limit; i > 0; ) { // rule L1
i--;
int type = TextCharProp.getBIDIClass (mLine.getBreakData (i));
if (type == TextCharProp.BIDI_WS) {
if (mLine.tabAt(i) != null) { // if space substituted for tab
type = TextCharProp.BIDI_S; // use tab type
}
}
switch (type) {
case TextCharProp.BIDI_B:
case TextCharProp.BIDI_S:
overrideLevel (i, defaultLevel, afeRun);
defaultWhitespaceLevels = true;
break;
case TextCharProp.BIDI_WS:
if (defaultWhitespaceLevels) {
overrideLevel (i, defaultLevel, afeRun);
}
break;
default:
if (! isExplicitControl (type)) {
defaultWhitespaceLevels = false;
}
}
if (mLevels[i] > maxLevel) {
maxLevel = mLevels[i];
}
}
return maxLevel;
}
/**
* Perform the BIDI reordering.
*
* This method reorders the characters in a wrapped line according to
* the results of the BIDI algorithm.
*
* @param start - Index in the parent raw line designating the start of
* this wrapped line.
* @param linit - limit in the parent raw line designating the end of
* this wrapped line.
* @param maxLevel - Maximum BIDI level within this wrapped line.
* @return Array of reordered character positions.
*/
private int[] reorderRuns (int start, int limit, int maxLevel) {
int length = limit - start;
if (length <= 0) {
return null;
}
int i;
int[] indexes = new int [length]; // TODO: cache and reuse this?
for (i = 0; i < length; i++) {
indexes[i] = i + start;
}
// This loop performs the actual reordering. Starting with the deepest
// runs (those with the highest level), reverse the text within the run.
// Iterate until level 1 (lowest RTL) has been reordered. Note that this
// is the brute force implementation from UAX9. The content of any given
// run will be reordered multiple times, equal to its run level. There
// are likely optimizations that could be applied if performance analysis
// highlights problems.
while (maxLevel > 0) { // rule L2
int runStart = -1;
for (i = 0; i < length; i++) {
if (mLevels[i+start] >= maxLevel) {
if (runStart < 0) {
runStart = i;
}
} else {
if (runStart >= 0) {
reverseRun (indexes, runStart, i);
runStart = -1;
}
}
}
if (runStart >= 0) {
reverseRun (indexes, runStart, length);
}
maxLevel--;
}
// AFE positioning is based on the assumption that combining characters
// are rendered after their base characters. This means that such
// sequences in RTL text must be reversed to LTR order.
int accentRunStart = -1;
for (i = 0; i < length; i++) { // rule L3
int index = indexes[i];
int breakType = mLine.getBreakClass (index);
if (breakType == TextCharProp.BREAK_CM) {
if (accentRunStart < 0) {
accentRunStart = i;
}
} else {
if ((accentRunStart >= 0) && isRTL (mLevels[index])) {
reverseRun (indexes, accentRunStart, i + 1);
}
accentRunStart = -1;
}
}
return indexes;
}
private void overrideLevel (int index, byte newLevel, AFERun afeRun) {
mLevels[index] = newLevel;
afeRun.getElement(index).setBIDILevel (newLevel);
}
private static boolean isRTL (int level) {
return (level & 1) != 0;
}
private static int getDirectionFromLevel (int level) {
return isRTL (level) ? TextCharProp.BIDI_R : TextCharProp.BIDI_L;
}
private static int nextEvenLevel (int level) {
return isRTL (level) ? (level + 1) : (level + 2);
}
private static int nextOddLevel (int level) {
return isRTL (level) ? (level + 2) : (level + 1);
}
private int getDefaultLevel () {
return mLine.isRTL() ? 1 : 0;
}
private static int resolveDirection (int override, int direction) {
return (override == TextCharProp.BIDI_ON) ? direction : override;
}
private static boolean isStrong (int direction) {
switch (direction) {
case TextCharProp.BIDI_AL:
case TextCharProp.BIDI_L:
case TextCharProp.BIDI_LRE:
case TextCharProp.BIDI_LRO:
case TextCharProp.BIDI_R:
case TextCharProp.BIDI_RLE:
case TextCharProp.BIDI_RLO:
return true;
}
return false;
}
private static boolean isExplicitControl (int direction) {
switch (direction) {
case TextCharProp.BIDI_LRE:
case TextCharProp.BIDI_LRO:
case TextCharProp.BIDI_RLE:
case TextCharProp.BIDI_RLO:
case TextCharProp.BIDI_PDF:
return true;
}
return false;
}
private final int findNonExplicit (int index, int limit) {
for (; index < limit; index++) {
if (! isExplicitControl (mDirections[index])) {
break;
}
}
return index;
}
private static void reverseRun (int[] indexes, int start, int limit) {
int mid = (start + limit) / 2;
int end = limit - 1;
while (start < mid) {
int swap = indexes[start];
indexes[start] = indexes[end];
indexes[end] = swap;
start++;
end--;
}
}
}