1:
37:
38:
39: package ;
40:
41: import ;
42: import ;
43: import ;
44:
45:
46:
55: public final class Bidi
56: {
57:
62: public static final int DIRECTION_DEFAULT_LEFT_TO_RIGHT = -2;
63:
64:
69: public static final int DIRECTION_DEFAULT_RIGHT_TO_LEFT = -1;
70:
71:
74: public static final int DIRECTION_LEFT_TO_RIGHT = 0;
75:
76:
79: public static final int DIRECTION_RIGHT_TO_LEFT = 1;
80:
81:
82: private static final int LTOR = 1 << DIRECTION_LEFT_TO_RIGHT;
83: private static final int RTOL = 1 << DIRECTION_RIGHT_TO_LEFT;
84:
85:
86:
87:
88:
89: private char[] text;
90: private int textOffset;
91:
92: private byte[] embeddings;
93: private int embeddingOffset;
94:
95: private int length;
96:
97: private int flags;
98:
99:
100:
101:
102:
103: private int baseEmbedding;
104:
105: private byte[] types;
106:
107: private byte[] levels;
108:
109:
110:
111:
112: private ArrayList formatterIndices;
113:
114:
115: private int[] runs;
116:
117:
118:
119: private int resultFlags;
120:
121:
143: public Bidi(AttributedCharacterIterator iter)
144: {
145:
146:
147:
148: Object val = iter.getAttribute(TextAttribute.RUN_DIRECTION);
149: if (val == TextAttribute.RUN_DIRECTION_LTR)
150: this.flags = DIRECTION_LEFT_TO_RIGHT;
151: else if (val == TextAttribute.RUN_DIRECTION_RTL)
152: this.flags = DIRECTION_RIGHT_TO_LEFT;
153: else
154: this.flags = DIRECTION_DEFAULT_LEFT_TO_RIGHT;
155:
156:
157:
158:
159: NumericShaper shaper = null;
160: val = iter.getAttribute(TextAttribute.NUMERIC_SHAPING);
161: if (val instanceof NumericShaper)
162: shaper = (NumericShaper) val;
163:
164: char[] text = new char[iter.getEndIndex() - iter.getBeginIndex()];
165: this.embeddings = new byte[this.text.length];
166: this.embeddingOffset = 0;
167: this.length = text.length;
168: for (int i = 0; i < this.text.length; ++i)
169: {
170: this.text[i] = iter.current();
171:
172: val = iter.getAttribute(TextAttribute.BIDI_EMBEDDING);
173: if (val instanceof Integer)
174: {
175: int ival = ((Integer) val).intValue();
176: byte bval;
177: if (ival < -62 || ival > 62)
178: bval = 0;
179: else
180: bval = (byte) ival;
181: this.embeddings[i] = bval;
182: }
183: }
184:
185:
186: if (shaper != null)
187: shaper.shape(this.text, 0, this.length);
188:
189: runBidi();
190: }
191:
192:
208: public Bidi(char[] text, int offset, byte[] embeddings, int embedOffset,
209: int length, int flags)
210: {
211: if (flags != DIRECTION_DEFAULT_LEFT_TO_RIGHT
212: && flags != DIRECTION_DEFAULT_RIGHT_TO_LEFT
213: && flags != DIRECTION_LEFT_TO_RIGHT
214: && flags != DIRECTION_RIGHT_TO_LEFT)
215: throw new IllegalArgumentException("unrecognized 'flags' argument: "
216: + flags);
217: this.text = text;
218: this.textOffset = offset;
219: this.embeddings = embeddings;
220: this.embeddingOffset = embedOffset;
221: this.length = length;
222: this.flags = flags;
223:
224: runBidi();
225: }
226:
227:
233: public Bidi(String text, int flags)
234: {
235: if (flags != DIRECTION_DEFAULT_LEFT_TO_RIGHT
236: && flags != DIRECTION_DEFAULT_RIGHT_TO_LEFT
237: && flags != DIRECTION_LEFT_TO_RIGHT
238: && flags != DIRECTION_RIGHT_TO_LEFT)
239: throw new IllegalArgumentException("unrecognized 'flags' argument: "
240: + flags);
241:
242:
243:
244:
245: this.text = text.toCharArray();
246: this.textOffset = 0;
247: this.embeddings = null;
248: this.embeddingOffset = 0;
249: this.length = text.length();
250: this.flags = flags;
251:
252: runBidi();
253: }
254:
255:
259: private void computeTypes()
260: {
261: types = new byte[length];
262: for (int i = 0; i < length; ++i)
263: types[i] = Character.getDirectionality(text[textOffset + i]);
264: }
265:
266:
271: private int computeParagraphEmbeddingLevel()
272: {
273:
274: if (flags == DIRECTION_LEFT_TO_RIGHT
275: || flags == DIRECTION_RIGHT_TO_LEFT)
276: return flags;
277:
278:
279:
280:
281: for (int i = 0; i < length; ++i)
282: {
283: int dir = types[i];
284: if (dir == Character.DIRECTIONALITY_LEFT_TO_RIGHT)
285: return DIRECTION_LEFT_TO_RIGHT;
286: if (dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT
287: || dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT)
288: return DIRECTION_RIGHT_TO_LEFT;
289: }
290: return (flags == DIRECTION_DEFAULT_LEFT_TO_RIGHT
291: ? DIRECTION_LEFT_TO_RIGHT
292: : DIRECTION_RIGHT_TO_LEFT);
293: }
294:
295:
300: private void computeExplicitLevels()
301: {
302: levels = new byte[length];
303: byte currentEmbedding = (byte) baseEmbedding;
304:
305:
306: byte directionalOverride = -1;
307:
308:
309:
310:
311: final int MAX_DEPTH = 62;
312: byte[] embeddingStack = new byte[MAX_DEPTH];
313: int sp = 0;
314:
315: for (int i = 0; i < length; ++i)
316: {
317:
318:
319: if (embeddings != null && embeddings[embeddingOffset + i] != 0)
320: {
321:
322:
323:
324: currentEmbedding = embeddings[embeddingOffset + i];
325: if (currentEmbedding < 0)
326: {
327: currentEmbedding = (byte) -currentEmbedding;
328: directionalOverride
329: = (((currentEmbedding % 2) == 0)
330: ? Character.DIRECTIONALITY_LEFT_TO_RIGHT
331: : Character.DIRECTIONALITY_RIGHT_TO_LEFT);
332: }
333: else
334: directionalOverride = -1;
335: continue;
336: }
337:
338: boolean isLtoR = false;
339: boolean isSpecial = true;
340: switch (types[i])
341: {
342: case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
343: case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
344: isLtoR = true;
345:
346: case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
347: case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
348: {
349: byte newEmbedding;
350: if (isLtoR)
351: {
352:
353: newEmbedding = (byte) ((currentEmbedding & ~1) + 2);
354: }
355: else
356: {
357:
358: newEmbedding = (byte) ((currentEmbedding + 1) | 1);
359: }
360:
361: if (newEmbedding < MAX_DEPTH)
362: {
363:
364:
365: if (directionalOverride != -1)
366: currentEmbedding |= Byte.MIN_VALUE;
367: embeddingStack[sp++] = currentEmbedding;
368: currentEmbedding = newEmbedding;
369: if (types[i] == Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE)
370: directionalOverride = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
371: else if (types[i] == Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE)
372: directionalOverride = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
373: else
374: directionalOverride = -1;
375: }
376: }
377: break;
378: case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT:
379: {
380:
381:
382: if (sp == 0)
383: {
384:
385: break;
386: }
387: byte newEmbedding = embeddingStack[--sp];
388: currentEmbedding = (byte) (newEmbedding & 0x7f);
389: if (newEmbedding < 0)
390: directionalOverride
391: = (((newEmbedding & 1) == 0)
392: ? Character.DIRECTIONALITY_LEFT_TO_RIGHT
393: : Character.DIRECTIONALITY_RIGHT_TO_LEFT);
394: else
395: directionalOverride = -1;
396: }
397: break;
398: default:
399: isSpecial = false;
400: break;
401: }
402: levels[i] = currentEmbedding;
403: if (isSpecial)
404: {
405:
406: if (formatterIndices == null)
407: formatterIndices = new ArrayList();
408: formatterIndices.add(Integer.valueOf(i));
409: }
410: else if (directionalOverride != -1)
411: types[i] = directionalOverride;
412: }
413:
414:
415:
416:
417:
418:
419:
420: if (formatterIndices == null)
421: return;
422: int output = 0, input = 0;
423: final int size = formatterIndices.size();
424: for (int i = 0; i <= size; ++i)
425: {
426: int nextFmt;
427: if (i == size)
428: nextFmt = length;
429: else
430: nextFmt = ((Integer) formatterIndices.get(i)).intValue();
431:
432: int len = nextFmt - input;
433: System.arraycopy(levels, input, levels, output, len);
434: System.arraycopy(types, input, types, output, len);
435: output += len;
436: input = nextFmt + 1;
437: }
438: length -= formatterIndices.size();
439: }
440:
441:
449: private void computeRuns()
450: {
451: int runCount = 0;
452: int currentEmbedding = baseEmbedding;
453: for (int i = 0; i < length; ++i)
454: {
455: if (levels[i] != currentEmbedding)
456: {
457: currentEmbedding = levels[i];
458: ++runCount;
459: }
460: }
461:
462:
463:
464:
465: if (runs == null || runs.length != runCount + 1)
466: runs = new int[runCount + 1];
467: int where = 0;
468: int lastRunStart = 0;
469: currentEmbedding = baseEmbedding;
470: for (int i = 0; i < length; ++i)
471: {
472: if (levels[i] != currentEmbedding)
473: {
474: runs[where++] = lastRunStart;
475: lastRunStart = i;
476: currentEmbedding = levels[i];
477: }
478: }
479: runs[where++] = lastRunStart;
480: }
481:
482:
486: private void resolveWeakTypes()
487: {
488: final int runCount = getRunCount();
489:
490: int previousLevel = baseEmbedding;
491: for (int run = 0; run < runCount; ++run)
492: {
493: int start = getRunStart(run);
494: int end = getRunLimit(run);
495: int level = getRunLevel(run);
496:
497:
498: byte sor = (((Math.max(previousLevel, level) % 2) == 0)
499: ? Character.DIRECTIONALITY_LEFT_TO_RIGHT
500: : Character.DIRECTIONALITY_RIGHT_TO_LEFT);
501: int nextLevel;
502: if (run == runCount - 1)
503: nextLevel = baseEmbedding;
504: else
505: nextLevel = getRunLevel(run + 1);
506: byte eor = (((Math.max(level, nextLevel) % 2) == 0)
507: ? Character.DIRECTIONALITY_LEFT_TO_RIGHT
508: : Character.DIRECTIONALITY_RIGHT_TO_LEFT);
509:
510: byte prevType = sor;
511: byte prevStrongType = sor;
512: for (int i = start; i < end; ++i)
513: {
514: final byte nextType = (i == end - 1) ? eor : types[i + 1];
515:
516:
517: if (types[i] == Character.DIRECTIONALITY_NONSPACING_MARK)
518: types[i] = prevType;
519: else
520: prevType = types[i];
521:
522:
523: if (types[i] == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
524: {
525: if (prevStrongType == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
526: types[i] = Character.DIRECTIONALITY_ARABIC_NUMBER;
527: }
528: else if (types[i] == Character.DIRECTIONALITY_LEFT_TO_RIGHT
529: || types[i] == Character.DIRECTIONALITY_RIGHT_TO_LEFT
530: || types[i] == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
531: prevStrongType = types[i];
532:
533:
534: if (types[i] == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
535: types[i] = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
536:
537:
538: if (prevType == Character.DIRECTIONALITY_EUROPEAN_NUMBER
539: && nextType == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
540: {
541: if (types[i] == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR
542: || types[i] == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR)
543: types[i] = nextType;
544: }
545: else if (prevType == Character.DIRECTIONALITY_ARABIC_NUMBER
546: && nextType == Character.DIRECTIONALITY_ARABIC_NUMBER
547: && types[i] == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR)
548: types[i] = nextType;
549:
550:
551:
552:
553: if (types[i] == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR
554: || types[i] == Character.DIRECTIONALITY_BOUNDARY_NEUTRAL)
555: {
556: if (prevType == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
557: types[i] = prevType;
558: else
559: {
560:
561:
562: int j = i + 1;
563: while (j < end
564: && (types[j] == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR
565: || types[j] == Character.DIRECTIONALITY_BOUNDARY_NEUTRAL))
566: ++j;
567: if (j < end
568: && types[j] == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
569: {
570:
571: for (int k = i; k < j; ++k)
572: types[k] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
573: }
574: }
575: }
576:
577:
578:
579: if (types[i] == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR
580: || types[i] == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR
581: || types[i] == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR
582: || types[i] == Character.DIRECTIONALITY_BOUNDARY_NEUTRAL)
583: types[i] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
584:
585:
586: if (prevStrongType == Character.DIRECTIONALITY_LEFT_TO_RIGHT
587: && types[i] == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
588: types[i] = prevStrongType;
589: }
590:
591: previousLevel = level;
592: }
593: }
594:
595:
599: private void resolveNeutralTypes()
600: {
601:
602: final int runCount = getRunCount();
603:
604: int previousLevel = baseEmbedding;
605: for (int run = 0; run < runCount; ++run)
606: {
607: int start = getRunStart(run);
608: int end = getRunLimit(run);
609: int level = getRunLevel(run);
610:
611: byte embeddingDirection
612: = (((level % 2) == 0) ? Character.DIRECTIONALITY_LEFT_TO_RIGHT
613: : Character.DIRECTIONALITY_RIGHT_TO_LEFT);
614:
615: byte sor = (((Math.max(previousLevel, level) % 2) == 0)
616: ? Character.DIRECTIONALITY_LEFT_TO_RIGHT
617: : Character.DIRECTIONALITY_RIGHT_TO_LEFT);
618: int nextLevel;
619: if (run == runCount - 1)
620: nextLevel = baseEmbedding;
621: else
622: nextLevel = getRunLevel(run + 1);
623: byte eor = (((Math.max(level, nextLevel) % 2) == 0)
624: ? Character.DIRECTIONALITY_LEFT_TO_RIGHT
625: : Character.DIRECTIONALITY_RIGHT_TO_LEFT);
626:
627: byte prevStrong = sor;
628: int neutralStart = -1;
629: for (int i = start; i <= end; ++i)
630: {
631: byte newStrong = -1;
632: byte thisType = i == end ? eor : types[i];
633: switch (thisType)
634: {
635: case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
636: newStrong = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
637: break;
638: case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
639: case Character.DIRECTIONALITY_ARABIC_NUMBER:
640: case Character.DIRECTIONALITY_EUROPEAN_NUMBER:
641: newStrong = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
642: break;
643: case Character.DIRECTIONALITY_BOUNDARY_NEUTRAL:
644: case Character.DIRECTIONALITY_OTHER_NEUTRALS:
645: case Character.DIRECTIONALITY_SEGMENT_SEPARATOR:
646: case Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR:
647: if (neutralStart == -1)
648: neutralStart = i;
649: break;
650: }
651:
652: if (newStrong != -1)
653: {
654: if (neutralStart != -1)
655: {
656: byte override = (prevStrong == newStrong
657: ? prevStrong
658: : embeddingDirection);
659: for (int j = neutralStart; j < i; ++j)
660: types[i] = override;
661: }
662: prevStrong = newStrong;
663: neutralStart = -1;
664: }
665: }
666:
667: previousLevel = level;
668: }
669: }
670:
671:
675: private void resolveImplicitLevels()
676: {
677:
678: for (int i = 0; i < length; ++i)
679: {
680: if ((levels[i] & 1) == 0)
681: {
682: if (types[i] == Character.DIRECTIONALITY_RIGHT_TO_LEFT)
683: ++levels[i];
684: else if (types[i] == Character.DIRECTIONALITY_ARABIC_NUMBER
685: || types[i] == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
686: levels[i] += 2;
687: }
688: else
689: {
690: if (types[i] == Character.DIRECTIONALITY_LEFT_TO_RIGHT
691: || types[i] == Character.DIRECTIONALITY_ARABIC_NUMBER
692: || types[i] == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
693: ++levels[i];
694: }
695:
696:
697: resultFlags |= 1 << (levels[i] & 1);
698: }
699:
700: resultFlags |= 1 << baseEmbedding;
701: }
702:
703:
709: private void reinsertFormattingCodes()
710: {
711: if (formatterIndices == null)
712: return;
713: int input = length;
714: int output = levels.length;
715:
716: for (int index = formatterIndices.size() - 1; index >= 0; --index)
717: {
718: int nextFmt = ((Integer) formatterIndices.get(index)).intValue();
719:
720:
721:
722:
723:
724: int len = output - nextFmt - 1;
725: output = nextFmt;
726: input -= len;
727:
728:
729: if (nextFmt + 1 < levels.length)
730: System.arraycopy(levels, input, levels, nextFmt + 1, len);
731:
732:
733: int rightLevel;
734: if (output == levels.length - 1)
735: rightLevel = baseEmbedding;
736: else
737: rightLevel = levels[output + 1];
738: int leftLevel;
739: if (input == 0)
740: leftLevel = baseEmbedding;
741: else
742: leftLevel = levels[input];
743: levels[output] = (byte) Math.max(leftLevel, rightLevel);
744: }
745: length = levels.length;
746: }
747:
748:
753: private void runBidi()
754: {
755: computeTypes();
756: baseEmbedding = computeParagraphEmbeddingLevel();
757: computeExplicitLevels();
758: computeRuns();
759: resolveWeakTypes();
760: resolveNeutralTypes();
761: resolveImplicitLevels();
762:
763: types = null;
764: reinsertFormattingCodes();
765:
766:
767: computeRuns();
768: }
769:
770:
774: public boolean baseIsLeftToRight()
775: {
776: return baseEmbedding == DIRECTION_LEFT_TO_RIGHT;
777: }
778:
779:
786: public Bidi createLineBidi(int start, int end)
787: {
788:
789:
790: int level = getLevelAt(start);
791: int flag = (((level % 2) == 0)
792: ? DIRECTION_LEFT_TO_RIGHT
793: : DIRECTION_RIGHT_TO_LEFT);
794: return new Bidi(text, textOffset + start,
795: embeddings, embeddingOffset + start,
796: end - start, flag);
797: }
798:
799:
802: public int getBaseLevel()
803: {
804: return baseEmbedding;
805: }
806:
807:
810: public int getLength()
811: {
812: return length;
813: }
814:
815:
823: public int getLevelAt(int offset)
824: {
825: if (offset < 0 || offset >= length)
826: return getBaseLevel();
827: return levels[offset];
828: }
829:
830:
834: public int getRunCount()
835: {
836: return runs.length;
837: }
838:
839:
844: public int getRunLevel(int which)
845: {
846: return levels[runs[which]];
847: }
848:
849:
856: public int getRunLimit(int which)
857: {
858: if (which == runs.length - 1)
859: return length;
860: return runs[which + 1];
861: }
862:
863:
868: public int getRunStart(int which)
869: {
870: return runs[which];
871: }
872:
873:
877: public boolean isLeftToRight()
878: {
879: return resultFlags == LTOR;
880: }
881:
882:
887: public boolean isMixed()
888: {
889: return resultFlags == (LTOR | RTOL);
890: }
891:
892:
896: public boolean isRightToLeft()
897: {
898: return resultFlags == RTOL;
899: }
900:
901:
905: public String toString()
906: {
907: return "Bidi Bidi Bidi I like you, Buck!";
908: }
909:
910:
922: public static void reorderVisually(byte[] levels, int levelOffset,
923: Object[] objs, int objOffset, int count)
924: {
925:
926:
927: byte[] levelCopy = new byte[count];
928:
929:
930: int max = 0;
931: int lowestOdd = 63;
932: for (int i = 0; i < count; ++i)
933: {
934: levelCopy[i] = levels[levelOffset + i];
935: max = Math.max(levelCopy[i], max);
936: if (levelCopy[i] % 2 != 0)
937: lowestOdd = Math.min(lowestOdd, levelCopy[i]);
938: }
939:
940:
941: for (int depth = max; depth >= lowestOdd; --depth)
942: {
943: int start = 0;
944: while (start < count)
945: {
946:
947: while (start < count && levelCopy[start] < depth)
948: ++start;
949: if (start == count)
950: break;
951:
952: int end = start + 1;
953: while (end < count && levelCopy[end] >= depth)
954: ++end;
955:
956:
957: for (int i = 0; i < (end - start) / 2; ++i)
958: {
959: byte tmpb = levelCopy[end - i - 1];
960: levelCopy[end - i - 1] = levelCopy[start + i];
961: levelCopy[start + i] = tmpb;
962: Object tmpo = objs[objOffset + end - i - 1];
963: objs[objOffset + end - i - 1] = objs[objOffset + start + i];
964: objs[objOffset + start + i] = tmpo;
965: }
966:
967:
968: start = end + 1;
969: }
970: }
971: }
972:
973:
981: public static boolean requiresBidi(char[] text, int start, int end)
982: {
983: for (int i = start; i < end; i++)
984: {
985: byte dir = Character.getDirectionality(text[i]);
986: if (dir != Character.DIRECTIONALITY_LEFT_TO_RIGHT
987: && dir != Character.DIRECTIONALITY_EUROPEAN_NUMBER
988: && dir != Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR
989: && dir != Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR
990: && dir != Character.DIRECTIONALITY_ARABIC_NUMBER
991: && dir != Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR
992: && dir != Character.DIRECTIONALITY_SEGMENT_SEPARATOR
993: && dir != Character.DIRECTIONALITY_WHITESPACE)
994: return true;
995: }
996:
997: return false;
998: }
999: }