in batik-gvt/src/main/java/org/apache/batik/gvt/text/BidiAttributedCharacterIterator.java [68:265]
public BidiAttributedCharacterIterator(AttributedCharacterIterator aci,
FontRenderContext frc,
int chunkStart) {
this.frc = frc;
this.chunkStart = chunkStart;
aci.first();
int numChars = aci.getEndIndex()-aci.getBeginIndex();
AttributedString as;
// Ideally we would do a 'quick' check on chars and
// attributes to decide if we really need to do bidi or not.
if (false) {
// Believe it or not this is much slower than the else case
// but the two are exactly equivalent (including the stripping
// of null keys/values).
as = new AttributedString(aci);
} else {
StringBuffer strB = new StringBuffer( numChars );
char c = aci.first();
for (int i = 0; i < numChars; i++) {
strB.append(c);
c = aci.next();
}
as = new AttributedString(strB.toString());
int start=aci.getBeginIndex();
int end =aci.getEndIndex();
int index = start;
while (index < end) {
aci.setIndex(index);
Map attrMap = aci.getAttributes();
int extent = aci.getRunLimit();
Map destMap = new HashMap(attrMap.size());
for (Object o : attrMap.entrySet()) {
// Font doesn't like getting attribute sets with
// null keys or values so we strip them here.
Map.Entry e = (Map.Entry) o;
Object key = e.getKey();
if (key == null) continue;
Object value = e.getValue();
if (value == null) continue;
destMap.put(key, value);
}
// System.out.println("Run: " + (index-start) + "->" +
// (extent-start) + " of " + numChars);
as.addAttributes (destMap, index-start, extent-start);
index = extent;
}
}
// We Just want it to do BIDI for us...
// In 1.4 we might be able to use the BIDI class...
TextLayout tl = new TextLayout(as.getIterator(), frc);
int[] charIndices = new int[numChars];
int[] charLevels = new int[numChars];
int runStart = 0;
int currBiDi = tl.getCharacterLevel(0);
charIndices[0] = 0;
charLevels [0] = currBiDi;
int maxBiDi = currBiDi;
for (int i = 1; i < numChars; i++) {
int newBiDi = tl.getCharacterLevel(i);
charIndices[i] = i;
charLevels [i] = newBiDi;
if (newBiDi != currBiDi) {
as.addAttribute
(GVTAttributedCharacterIterator.TextAttribute.BIDI_LEVEL,
currBiDi, runStart, i);
runStart = i;
currBiDi = newBiDi;
if (newBiDi > maxBiDi) maxBiDi = newBiDi;
}
}
as.addAttribute
(GVTAttributedCharacterIterator.TextAttribute.BIDI_LEVEL,
currBiDi, runStart, numChars);
aci = as.getIterator();
if ((runStart == 0) && (currBiDi==0)) {
// This avoids all the mucking about we need to do when
// bidi is actually performed for cases where it
// is not actually needed.
this.reorderedACI = aci;
newCharOrder = new int[numChars];
for (int i=0; i<numChars; i++)
newCharOrder[i] = chunkStart+i;
return;
}
// work out the new character order
newCharOrder = doBidiReorder(charIndices, charLevels,
numChars, maxBiDi);
// construct the string in the new order
StringBuffer reorderedString = new StringBuffer( numChars );
int reorderedFirstChar = 0;
for (int i = 0; i < numChars; i++) {
int srcIdx = newCharOrder[i];
char c = aci.setIndex(srcIdx);
if (srcIdx == 0) reorderedFirstChar = i;
// check for mirrored char
int bidiLevel = tl.getCharacterLevel(srcIdx);
if ((bidiLevel & 0x01) != 0) {
// bidi level is odd so writing dir is right to left
// So get the mirror version of the char if there
// is one.
c = (char)mirrorChar(c);
}
reorderedString.append(c);
}
// construct the reordered ACI
AttributedString reorderedAS
= new AttributedString(reorderedString.toString());
Map [] attrs = new Map[numChars];
int start=aci.getBeginIndex();
int end =aci.getEndIndex();
int index = start;
while (index < end) {
aci.setIndex(index);
Map attrMap = aci.getAttributes();
int extent = aci.getRunLimit();
for (int i=index; i<extent; i++)
attrs[i-start] = attrMap;
index = extent;
}
runStart=0;
Map prevAttrMap = attrs[newCharOrder[0]];
for (int i = 1; i < numChars; i++) {
Map attrMap = attrs[newCharOrder[i]];
if (attrMap != prevAttrMap) {
// Change in attrs set last run...
reorderedAS.addAttributes(prevAttrMap, runStart, i);
prevAttrMap = attrMap;
runStart = i;
}
}
reorderedAS.addAttributes(prevAttrMap, runStart, numChars);
// transfer any position atttributes to the new first char
aci.first();
Float x = (Float) aci.getAttribute
(GVTAttributedCharacterIterator.TextAttribute.X);
if (x != null && !x.isNaN()) {
reorderedAS.addAttribute
(GVTAttributedCharacterIterator.TextAttribute.X,
FLOAT_NAN, reorderedFirstChar, reorderedFirstChar+1);
reorderedAS.addAttribute
(GVTAttributedCharacterIterator.TextAttribute.X, x, 0, 1);
}
Float y = (Float) aci.getAttribute
(GVTAttributedCharacterIterator.TextAttribute.Y);
if (y != null && !y.isNaN()) {
reorderedAS.addAttribute
(GVTAttributedCharacterIterator.TextAttribute.Y,
FLOAT_NAN, reorderedFirstChar, reorderedFirstChar+1);
reorderedAS.addAttribute
(GVTAttributedCharacterIterator.TextAttribute.Y, y, 0, 1);
}
Float dx = (Float) aci.getAttribute
(GVTAttributedCharacterIterator.TextAttribute.DX);
if (dx != null && !dx.isNaN()) {
reorderedAS.addAttribute
(GVTAttributedCharacterIterator.TextAttribute.DX,
FLOAT_NAN, reorderedFirstChar, reorderedFirstChar+1);
reorderedAS.addAttribute
(GVTAttributedCharacterIterator.TextAttribute.DX, dx, 0, 1);
}
Float dy = (Float) aci.getAttribute
(GVTAttributedCharacterIterator.TextAttribute.DY);
if (dy != null && !dy.isNaN()) {
reorderedAS.addAttribute
(GVTAttributedCharacterIterator.TextAttribute.DY,
FLOAT_NAN, reorderedFirstChar, reorderedFirstChar+1);
reorderedAS.addAttribute
(GVTAttributedCharacterIterator.TextAttribute.DY, dy, 0, 1);
}
// assign arabic form attributes to any arabic chars in the string
reorderedAS = ArabicTextHandler.assignArabicForms(reorderedAS);
// Shift the values to match the source text string...
for (int i=0; i<newCharOrder.length; i++) {
newCharOrder[i] += chunkStart;
}
reorderedACI = reorderedAS.getIterator();
}