1:
37:
38:
39: package ;
40:
41: import ;
42: import ;
43: import ;
44: import ;
45: import ;
46: import ;
47: import ;
48: import ;
49: import ;
50: import ;
51: import ;
52: import ;
53: import ;
54:
55:
58: public final class TextLayout implements Cloneable
59: {
60:
63: private class Run
64: {
65:
68: GlyphVector glyphVector;
69:
70:
73: Font font;
74:
75:
78: int runStart;
79:
80:
83: int runEnd;
84:
85:
88: float location;
89:
90:
97: Run(GlyphVector gv, Font f, int start, int end)
98: {
99: glyphVector = gv;
100: font = f;
101: runStart = start;
102: runEnd = end;
103: }
104:
105:
112: boolean isLeftToRight()
113: {
114: return (glyphVector.getLayoutFlags() & GlyphVector.FLAG_RUN_RTL) == 0;
115: }
116: }
117:
118:
121: private Run[] runs;
122:
123: private FontRenderContext frc;
124: private char[] string;
125: private int offset;
126: private int length;
127: private Rectangle2D boundsCache;
128: private LineMetrics lm;
129:
130:
134: private float totalAdvance = -1F;
135:
136:
139: private Rectangle2D naturalBounds;
140:
141:
145: private int[][] charIndices;
146:
147:
150: private boolean leftToRight;
151:
152:
155: private boolean hasWhitespace = false;
156:
157:
161: private Bidi bidi;
162:
163:
167: private int[] logicalToVisual;
168:
169:
173: private int[] visualToLogical;
174:
175:
178: private int hash;
179:
180:
183: public static final TextLayout.CaretPolicy DEFAULT_CARET_POLICY =
184: new CaretPolicy();
185:
186:
189: public TextLayout (String str, Font font, FontRenderContext frc)
190: {
191: this.frc = frc;
192: string = str.toCharArray();
193: offset = 0;
194: length = this.string.length;
195: lm = font.getLineMetrics(this.string, offset, length, frc);
196:
197:
198: getStringProperties();
199:
200: if (Bidi.requiresBidi(string, offset, offset + length))
201: {
202: bidi = new Bidi(str, leftToRight ? Bidi.DIRECTION_LEFT_TO_RIGHT
203: : Bidi.DIRECTION_RIGHT_TO_LEFT );
204: int rc = bidi.getRunCount();
205: byte[] table = new byte[ rc ];
206: for(int i = 0; i < table.length; i++)
207: table[i] = (byte)bidi.getRunLevel(i);
208:
209: runs = new Run[rc];
210: for(int i = 0; i < rc; i++)
211: {
212: int start = bidi.getRunStart(i);
213: int end = bidi.getRunLimit(i);
214: if(start != end)
215: {
216: GlyphVector gv = font.layoutGlyphVector(frc,
217: string, start, end,
218: ((table[i] & 1) == 0) ? Font.LAYOUT_LEFT_TO_RIGHT
219: : Font.LAYOUT_RIGHT_TO_LEFT );
220: runs[i] = new Run(gv, font, start, end);
221: }
222: }
223: Bidi.reorderVisually( table, 0, runs, 0, runs.length );
224:
225: ArrayList cleaned = new ArrayList(rc);
226: for (int i = 0; i < rc; i++)
227: {
228: if (runs[i] != null)
229: cleaned.add(runs[i]);
230: }
231: runs = new Run[cleaned.size()];
232: runs = (Run[]) cleaned.toArray(runs);
233: }
234: else
235: {
236: GlyphVector gv = font.layoutGlyphVector( frc, string, offset, length,
237: leftToRight ? Font.LAYOUT_LEFT_TO_RIGHT
238: : Font.LAYOUT_RIGHT_TO_LEFT );
239: Run run = new Run(gv, font, 0, length);
240: runs = new Run[]{ run };
241: }
242: setCharIndices();
243: setupMappings();
244: layoutRuns();
245: }
246:
247: public TextLayout (String string,
248: Map<? extends AttributedCharacterIterator.Attribute, ?> attributes,
249: FontRenderContext frc)
250: {
251: this( string, new Font( attributes ), frc );
252: }
253:
254: public TextLayout (AttributedCharacterIterator text, FontRenderContext frc)
255: {
256:
257: this(getText(text), getFont(text), frc);
258: }
259:
260:
265: TextLayout(TextLayout t, int startIndex, int endIndex)
266: {
267: frc = t.frc;
268: boundsCache = null;
269: lm = t.lm;
270: leftToRight = t.leftToRight;
271:
272: if( endIndex > t.getCharacterCount() )
273: endIndex = t.getCharacterCount();
274: string = t.string;
275: offset = startIndex + offset;
276: length = endIndex - startIndex;
277:
278: int startingRun = t.charIndices[startIndex][0];
279: int nRuns = 1 + t.charIndices[endIndex - 1][0] - startingRun;
280:
281: runs = new Run[nRuns];
282: for( int i = 0; i < nRuns; i++ )
283: {
284: Run run = t.runs[i + startingRun];
285: GlyphVector gv = run.glyphVector;
286: Font font = run.font;
287:
288: int beginGlyphIndex = (i > 0) ? 0 : t.charIndices[startIndex][1];
289: int numEntries = ( i < nRuns - 1) ? gv.getNumGlyphs() :
290: 1 + t.charIndices[endIndex - 1][1] - beginGlyphIndex;
291:
292: int[] codes = gv.getGlyphCodes(beginGlyphIndex, numEntries, null);
293: gv = font.createGlyphVector(frc, codes);
294: runs[i] = new Run(gv, font, run.runStart - startIndex,
295: run.runEnd - startIndex);
296: }
297: runs[nRuns - 1].runEnd = endIndex - 1;
298:
299: setCharIndices();
300: setupMappings();
301: determineWhiteSpace();
302: layoutRuns();
303: }
304:
305: private void setCharIndices()
306: {
307: charIndices = new int[ getCharacterCount() ][2];
308: int i = 0;
309: int currentChar = 0;
310: for(int run = 0; run < runs.length; run++)
311: {
312: currentChar = -1;
313: Run current = runs[run];
314: GlyphVector gv = current.glyphVector;
315: for( int gi = 0; gi < gv.getNumGlyphs(); gi++)
316: {
317: if( gv.getGlyphCharIndex( gi ) != currentChar )
318: {
319: charIndices[ i ][0] = run;
320: charIndices[ i ][1] = gi;
321: currentChar = gv.getGlyphCharIndex( gi );
322: i++;
323: }
324: }
325: }
326: }
327:
328:
331: private void setupMappings()
332: {
333: int numChars = getCharacterCount();
334: logicalToVisual = new int[numChars];
335: visualToLogical = new int[numChars];
336: int lIndex = 0;
337: int vIndex = 0;
338:
339: for (int i = 0; i < runs.length; i++)
340: {
341: Run run = runs[i];
342: if (run.isLeftToRight())
343: {
344: for (lIndex = run.runStart; lIndex < run.runEnd; lIndex++)
345: {
346: logicalToVisual[lIndex] = vIndex;
347: visualToLogical[vIndex] = lIndex;
348: vIndex++;
349: }
350: }
351: else
352: {
353: for (lIndex = run.runEnd - 1; lIndex >= run.runStart; lIndex--)
354: {
355: logicalToVisual[lIndex] = vIndex;
356: visualToLogical[vIndex] = lIndex;
357: vIndex++;
358: }
359: }
360: }
361: }
362:
363: private static String getText(AttributedCharacterIterator iter)
364: {
365: StringBuffer sb = new StringBuffer();
366: int idx = iter.getIndex();
367: for(char c = iter.first(); c != CharacterIterator.DONE; c = iter.next())
368: sb.append(c);
369: iter.setIndex( idx );
370: return sb.toString();
371: }
372:
373: private static Font getFont(AttributedCharacterIterator iter)
374: {
375: Font f = (Font)iter.getAttribute(TextAttribute.FONT);
376: if( f == null )
377: {
378: int size;
379: Float i = (Float)iter.getAttribute(TextAttribute.SIZE);
380: if( i != null )
381: size = (int)i.floatValue();
382: else
383: size = 14;
384: f = new Font("Dialog", Font.PLAIN, size );
385: }
386: return f;
387: }
388:
389:
393: private void getStringProperties()
394: {
395: boolean gotDirection = false;
396: int i = offset;
397: int endOffs = offset + length;
398: leftToRight = true;
399: while( i < endOffs && !gotDirection )
400: switch( Character.getDirectionality(string[i++]) )
401: {
402: case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
403: case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
404: case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
405: gotDirection = true;
406: break;
407:
408: case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
409: case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
410: case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
411: case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
412: leftToRight = false;
413: gotDirection = true;
414: break;
415: }
416: determineWhiteSpace();
417: }
418:
419: private void determineWhiteSpace()
420: {
421:
422:
423: int i = offset + length - 1;
424: hasWhitespace = false;
425: while( i >= offset && Character.isWhitespace( string[i] ) )
426: i--;
427:
428: while( i >= offset )
429: if( Character.isWhitespace( string[i--] ) )
430: hasWhitespace = true;
431: }
432:
433: protected Object clone ()
434: {
435: return new TextLayout( this, 0, length);
436: }
437:
438: public void draw (Graphics2D g2, float x, float y)
439: {
440: for(int i = 0; i < runs.length; i++)
441: {
442: Run run = runs[i];
443: GlyphVector gv = run.glyphVector;
444: g2.drawGlyphVector(gv, x, y);
445: Rectangle2D r = gv.getLogicalBounds();
446: x += r.getWidth();
447: }
448: }
449:
450: public boolean equals (Object obj)
451: {
452: if( !( obj instanceof TextLayout) )
453: return false;
454:
455: return equals( (TextLayout) obj );
456: }
457:
458: public boolean equals (TextLayout tl)
459: {
460: if( runs.length != tl.runs.length )
461: return false;
462:
463: for( int i = 0; i < runs.length; i++ )
464: if( !runs[i].equals( tl.runs[i] ) )
465: return false;
466: return true;
467: }
468:
469: public float getAdvance ()
470: {
471: if (totalAdvance == -1F)
472: {
473: totalAdvance = 0f;
474: for(int i = 0; i < runs.length; i++)
475: {
476: Run run = runs[i];
477: GlyphVector gv = run.glyphVector;
478: totalAdvance += gv.getLogicalBounds().getWidth();
479: }
480: }
481: return totalAdvance;
482: }
483:
484: public float getAscent ()
485: {
486: return lm.getAscent();
487: }
488:
489: public byte getBaseline ()
490: {
491: return (byte)lm.getBaselineIndex();
492: }
493:
494: public float[] getBaselineOffsets ()
495: {
496: return lm.getBaselineOffsets();
497: }
498:
499: public Shape getBlackBoxBounds (int firstEndpoint, int secondEndpoint)
500: {
501: if( secondEndpoint - firstEndpoint <= 0 )
502: return new Rectangle2D.Float();
503:
504: if( firstEndpoint < 0 || secondEndpoint > getCharacterCount())
505: return new Rectangle2D.Float();
506:
507: GeneralPath gp = new GeneralPath();
508:
509: int ri = charIndices[ firstEndpoint ][0];
510: int gi = charIndices[ firstEndpoint ][1];
511:
512: double advance = 0;
513:
514: for( int i = 0; i < ri; i++ )
515: {
516: Run run = runs[i];
517: GlyphVector gv = run.glyphVector;
518: advance += gv.getLogicalBounds().getWidth();
519: }
520:
521: for( int i = ri; i <= charIndices[ secondEndpoint - 1 ][0]; i++ )
522: {
523: Run run = runs[i];
524: GlyphVector gv = run.glyphVector;
525: int dg;
526: if( i == charIndices[ secondEndpoint - 1 ][0] )
527: dg = charIndices[ secondEndpoint - 1][1];
528: else
529: dg = gv.getNumGlyphs() - 1;
530:
531: for( int j = 0; j <= dg; j++ )
532: {
533: Rectangle2D r2 = (gv.getGlyphVisualBounds( j )).
534: getBounds2D();
535: Point2D p = gv.getGlyphPosition( j );
536: r2.setRect( advance + r2.getX(), r2.getY(),
537: r2.getWidth(), r2.getHeight() );
538: gp.append(r2, false);
539: }
540:
541: advance += gv.getLogicalBounds().getWidth();
542: }
543: return gp;
544: }
545:
546: public Rectangle2D getBounds()
547: {
548: if( boundsCache == null )
549: boundsCache = getOutline(new AffineTransform()).getBounds();
550: return boundsCache;
551: }
552:
553: public float[] getCaretInfo (TextHitInfo hit)
554: {
555: return getCaretInfo(hit, getNaturalBounds());
556: }
557:
558: public float[] getCaretInfo (TextHitInfo hit, Rectangle2D bounds)
559: {
560: float[] info = new float[2];
561: int index = hit.getCharIndex();
562: boolean leading = hit.isLeadingEdge();
563:
564: Run run;
565:
566: if (index >= length)
567: {
568: info[0] = getAdvance();
569: info[1] = 0;
570: }
571: else
572: {
573: if (index < 0)
574: {
575: run = runs[0];
576: index = 0;
577: leading = true;
578: }
579: else
580: run = findRunAtIndex(index);
581:
582: int glyphIndex = index - run.runStart;
583: Shape glyphBounds = run.glyphVector.getGlyphLogicalBounds(glyphIndex);
584: Rectangle2D glyphRect = glyphBounds.getBounds2D();
585: if (isVertical())
586: {
587: if (leading)
588: info[0] = (float) glyphRect.getMinY();
589: else
590: info[0] = (float) glyphRect.getMaxY();
591: }
592: else
593: {
594: if (leading)
595: info[0] = (float) glyphRect.getMinX();
596: else
597: info[0] = (float) glyphRect.getMaxX();
598: }
599: info[0] += run.location;
600: info[1] = run.font.getItalicAngle();
601: }
602: return info;
603: }
604:
605: public Shape getCaretShape(TextHitInfo hit)
606: {
607: return getCaretShape(hit, getBounds());
608: }
609:
610: public Shape getCaretShape(TextHitInfo hit, Rectangle2D bounds)
611: {
612:
613: float[] info = getCaretInfo(hit);
614: float x1 = info[0];
615: float y1 = (float) bounds.getMinY();
616: float x2 = info[0];
617: float y2 = (float) bounds.getMaxY();
618: if (info[1] != 0)
619: {
620:
621: x1 -= y1 * info[1];
622: x2 -= y2 * info[1];
623: }
624: GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD, 2);
625: path.moveTo(x1, y1);
626: path.lineTo(x2, y2);
627: return path;
628: }
629:
630: public Shape[] getCaretShapes(int offset)
631: {
632: return getCaretShapes(offset, getNaturalBounds());
633: }
634:
635: public Shape[] getCaretShapes(int offset, Rectangle2D bounds)
636: {
637: return getCaretShapes(offset, bounds, DEFAULT_CARET_POLICY);
638: }
639:
640: public Shape[] getCaretShapes(int offset, Rectangle2D bounds,
641: CaretPolicy policy)
642: {
643:
644:
645: Shape[] carets = new Shape[2];
646: TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
647: int caretHit1 = hitToCaret(hit1);
648: TextHitInfo hit2 = hit1.getOtherHit();
649: int caretHit2 = hitToCaret(hit2);
650: if (caretHit1 == caretHit2)
651: {
652: carets[0] = getCaretShape(hit1);
653: carets[1] = null;
654: }
655: else
656: {
657: Shape caret1 = getCaretShape(hit1);
658: Shape caret2 = getCaretShape(hit2);
659: TextHitInfo strong = policy.getStrongCaret(hit1, hit2, this);
660: if (strong == hit1)
661: {
662: carets[0] = caret1;
663: carets[1] = caret2;
664: }
665: else
666: {
667: carets[0] = caret2;
668: carets[1] = caret1;
669: }
670: }
671: return carets;
672: }
673:
674: public int getCharacterCount ()
675: {
676: return length;
677: }
678:
679: public byte getCharacterLevel (int index)
680: {
681: byte level;
682: if( bidi == null )
683: level = 0;
684: else
685: level = (byte) bidi.getLevelAt(index);
686: return level;
687: }
688:
689: public float getDescent ()
690: {
691: return lm.getDescent();
692: }
693:
694: public TextLayout getJustifiedLayout (float justificationWidth)
695: {
696: TextLayout newLayout = (TextLayout)clone();
697:
698: if( hasWhitespace )
699: newLayout.handleJustify( justificationWidth );
700:
701: return newLayout;
702: }
703:
704: public float getLeading ()
705: {
706: return lm.getLeading();
707: }
708:
709: public Shape getLogicalHighlightShape (int firstEndpoint, int secondEndpoint)
710: {
711: return getLogicalHighlightShape( firstEndpoint, secondEndpoint,
712: getBounds() );
713: }
714:
715: public Shape getLogicalHighlightShape (int firstEndpoint, int secondEndpoint,
716: Rectangle2D bounds)
717: {
718: if( secondEndpoint - firstEndpoint <= 0 )
719: return new Rectangle2D.Float();
720:
721: if( firstEndpoint < 0 || secondEndpoint > getCharacterCount())
722: return new Rectangle2D.Float();
723:
724: Rectangle2D r = null;
725: int ri = charIndices[ firstEndpoint ][0];
726: int gi = charIndices[ firstEndpoint ][1];
727:
728: double advance = 0;
729:
730: for( int i = 0; i < ri; i++ )
731: advance += runs[i].glyphVector.getLogicalBounds().getWidth();
732:
733: for( int i = ri; i <= charIndices[ secondEndpoint - 1 ][0]; i++ )
734: {
735: Run run = runs[i];
736: GlyphVector gv = run.glyphVector;
737: int dg;
738: if( i == charIndices[ secondEndpoint - 1 ][0] )
739: dg = charIndices[ secondEndpoint - 1][1];
740: else
741: dg = gv.getNumGlyphs() - 1;
742:
743: for(; gi <= dg; gi++ )
744: {
745: Rectangle2D r2 = (gv.getGlyphLogicalBounds( gi )).
746: getBounds2D();
747: if( r == null )
748: r = r2;
749: else
750: r = r.createUnion(r2);
751: }
752: gi = 0;
753:
754: advance += gv.getLogicalBounds().getWidth();
755: }
756:
757: return r;
758: }
759:
760: public int[] getLogicalRangesForVisualSelection (TextHitInfo firstEndpoint,
761: TextHitInfo secondEndpoint)
762: {
763:
764: checkHitInfo(firstEndpoint);
765: checkHitInfo(secondEndpoint);
766:
767:
768: int start = hitToCaret(firstEndpoint);
769: int end = hitToCaret(secondEndpoint);
770: if (start > end)
771: {
772:
773: int temp = start;
774: start = end;
775: end = temp;
776: }
777:
778:
779: boolean[] include = new boolean[length];
780: for (int i = start; i < end; i++)
781: {
782: include[visualToLogical[i]] = true;
783: }
784:
785:
786: int numRuns = 0;
787: boolean in = false;
788: for (int i = 0; i < length; i++)
789: {
790: if (include[i] != in)
791: {
792: in = ! in;
793: if (in)
794: numRuns++;
795: }
796: }
797:
798:
799: int[] ranges = new int[numRuns * 2];
800: int index = 0;
801: in = false;
802: for (int i = 0; i < length; i++)
803: {
804: if (include[i] != in)
805: {
806: ranges[index] = i;
807: index++;
808: in = ! in;
809: }
810: }
811:
812: if (in)
813: ranges[index] = length;
814:
815: return ranges;
816: }
817:
818: public TextHitInfo getNextLeftHit(int offset)
819: {
820: return getNextLeftHit(offset, DEFAULT_CARET_POLICY);
821: }
822:
823: public TextHitInfo getNextLeftHit(int offset, CaretPolicy policy)
824: {
825: if (policy == null)
826: throw new IllegalArgumentException("Null policy not allowed");
827: if (offset < 0 || offset > length)
828: throw new IllegalArgumentException("Offset out of bounds");
829:
830: TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
831: TextHitInfo hit2 = hit1.getOtherHit();
832:
833: TextHitInfo strong = policy.getStrongCaret(hit1, hit2, this);
834: TextHitInfo next = getNextLeftHit(strong);
835: TextHitInfo ret = null;
836: if (next != null)
837: {
838: TextHitInfo next2 = getVisualOtherHit(next);
839: ret = policy.getStrongCaret(next2, next, this);
840: }
841: return ret;
842: }
843:
844: public TextHitInfo getNextLeftHit (TextHitInfo hit)
845: {
846: checkHitInfo(hit);
847: int index = hitToCaret(hit);
848: TextHitInfo next = null;
849: if (index != 0)
850: {
851: index--;
852: next = caretToHit(index);
853: }
854: return next;
855: }
856:
857: public TextHitInfo getNextRightHit(int offset)
858: {
859: return getNextRightHit(offset, DEFAULT_CARET_POLICY);
860: }
861:
862: public TextHitInfo getNextRightHit(int offset, CaretPolicy policy)
863: {
864: if (policy == null)
865: throw new IllegalArgumentException("Null policy not allowed");
866: if (offset < 0 || offset > length)
867: throw new IllegalArgumentException("Offset out of bounds");
868:
869: TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
870: TextHitInfo hit2 = hit1.getOtherHit();
871:
872: TextHitInfo next = getNextRightHit(policy.getStrongCaret(hit1, hit2, this));
873: TextHitInfo ret = null;
874: if (next != null)
875: {
876: TextHitInfo next2 = getVisualOtherHit(next);
877: ret = policy.getStrongCaret(next2, next, this);
878: }
879: return ret;
880: }
881:
882: public TextHitInfo getNextRightHit(TextHitInfo hit)
883: {
884: checkHitInfo(hit);
885: int index = hitToCaret(hit);
886: TextHitInfo next = null;
887: if (index < length)
888: {
889: index++;
890: next = caretToHit(index);
891: }
892: return next;
893: }
894:
895: public Shape getOutline (AffineTransform tx)
896: {
897: float x = 0f;
898: GeneralPath gp = new GeneralPath();
899: for(int i = 0; i < runs.length; i++)
900: {
901: GlyphVector gv = runs[i].glyphVector;
902: gp.append( gv.getOutline( x, 0f ), false );
903: Rectangle2D r = gv.getLogicalBounds();
904: x += r.getWidth();
905: }
906: if( tx != null )
907: gp.transform( tx );
908: return gp;
909: }
910:
911: public float getVisibleAdvance ()
912: {
913: float totalAdvance = 0f;
914:
915: if( runs.length <= 0 )
916: return 0f;
917:
918:
919: if( !Character.isWhitespace( string[offset + length - 1]) )
920: return getAdvance();
921:
922:
923: for(int i = 0; i < runs.length - 1; i++)
924: totalAdvance += runs[i].glyphVector.getLogicalBounds().getWidth();
925:
926: int lastRun = runs[runs.length - 1].runStart;
927: int j = length - 1;
928: while( j >= lastRun && Character.isWhitespace( string[j] ) ) j--;
929:
930: if( j < lastRun )
931: return totalAdvance;
932:
933: int lastNonWSChar = j - lastRun;
934: j = 0;
935: while( runs[ runs.length - 1 ].glyphVector.getGlyphCharIndex( j )
936: <= lastNonWSChar )
937: {
938: totalAdvance += runs[ runs.length - 1 ].glyphVector
939: .getGlyphLogicalBounds( j )
940: .getBounds2D().getWidth();
941: j ++;
942: }
943:
944: return totalAdvance;
945: }
946:
947: public Shape getVisualHighlightShape (TextHitInfo firstEndpoint,
948: TextHitInfo secondEndpoint)
949: {
950: return getVisualHighlightShape( firstEndpoint, secondEndpoint,
951: getBounds() );
952: }
953:
954: public Shape getVisualHighlightShape (TextHitInfo firstEndpoint,
955: TextHitInfo secondEndpoint,
956: Rectangle2D bounds)
957: {
958: GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
959: Shape caret1 = getCaretShape(firstEndpoint, bounds);
960: path.append(caret1, false);
961: Shape caret2 = getCaretShape(secondEndpoint, bounds);
962: path.append(caret2, false);
963:
964: int c1 = hitToCaret(firstEndpoint);
965: int c2 = hitToCaret(secondEndpoint);
966: if (c1 == 0 || c2 == 0)
967: {
968: path.append(left(bounds), false);
969: }
970:
971: if (c1 == length || c2 == length)
972: {
973: path.append(right(bounds), false);
974: }
975: return path.getBounds2D();
976: }
977:
978:
985: private Shape left(Rectangle2D b)
986: {
987: GeneralPath left = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
988: left.append(getCaretShape(TextHitInfo.beforeOffset(0)), false);
989: if (isVertical())
990: {
991: float y = (float) b.getMinY();
992: left.append(new Line2D.Float((float) b.getMinX(), y,
993: (float) b.getMaxX(), y), false);
994: }
995: else
996: {
997: float x = (float) b.getMinX();
998: left.append(new Line2D.Float(x, (float) b.getMinY(),
999: x, (float) b.getMaxY()), false);
1000: }
1001: return left.getBounds2D();
1002: }
1003:
1004:
1013: private Shape right(Rectangle2D b)
1014: {
1015: GeneralPath right = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
1016: right.append(getCaretShape(TextHitInfo.afterOffset(length)), false);
1017: if (isVertical())
1018: {
1019: float y = (float) b.getMaxY();
1020: right.append(new Line2D.Float((float) b.getMinX(), y,
1021: (float) b.getMaxX(), y), false);
1022: }
1023: else
1024: {
1025: float x = (float) b.getMaxX();
1026: right.append(new Line2D.Float(x, (float) b.getMinY(),
1027: x, (float) b.getMaxY()), false);
1028: }
1029: return right.getBounds2D();
1030: }
1031:
1032: public TextHitInfo getVisualOtherHit (TextHitInfo hit)
1033: {
1034: checkHitInfo(hit);
1035: int hitIndex = hit.getCharIndex();
1036:
1037: int index;
1038: boolean leading;
1039: if (hitIndex == -1 || hitIndex == length)
1040: {
1041:
1042: int visual;
1043: if (isLeftToRight() == (hitIndex == -1))
1044: visual = 0;
1045: else
1046: visual = length - 1;
1047: index = visualToLogical[visual];
1048: if (isLeftToRight() == (hitIndex == -1))
1049: leading = isCharacterLTR(index);
1050: else
1051: leading = ! isCharacterLTR(index);
1052: }
1053: else
1054: {
1055:
1056: int visual = logicalToVisual[hitIndex];
1057: boolean b;
1058: if (isCharacterLTR(hitIndex) == hit.isLeadingEdge())
1059: {
1060: visual--;
1061: b = false;
1062: }
1063: else
1064: {
1065: visual++;
1066: b = true;
1067: }
1068: if (visual >= 0 && visual < length)
1069: {
1070: index = visualToLogical[visual];
1071: leading = b == isLeftToRight();
1072: }
1073: else
1074: {
1075: index = b == isLeftToRight() ? length : -1;
1076: leading = index == length;
1077: }
1078: }
1079: return leading ? TextHitInfo.leading(index) : TextHitInfo.trailing(index);
1080: }
1081:
1082:
1086: protected void handleJustify (float justificationWidth)
1087: {
1088:
1089:
1090: double deltaW = justificationWidth - getVisibleAdvance();
1091: int nglyphs = 0;
1092:
1093:
1094: int lastNWS = offset + length - 1;
1095: while( Character.isWhitespace( string[lastNWS] ) ) lastNWS--;
1096:
1097:
1098: int[] wsglyphs = new int[length * 10];
1099: for(int run = 0; run < runs.length; run++ )
1100: {
1101: Run current = runs[run];
1102: for(int i = 0; i < current.glyphVector.getNumGlyphs(); i++ )
1103: {
1104: int cindex = current.runStart
1105: + current.glyphVector.getGlyphCharIndex( i );
1106: if( Character.isWhitespace( string[cindex] ) )
1107:
1108: {
1109: wsglyphs[ nglyphs * 2 ] = run;
1110: wsglyphs[ nglyphs * 2 + 1] = i;
1111: nglyphs++;
1112: }
1113: }
1114: }
1115: deltaW = deltaW / nglyphs;
1116: double w = 0;
1117: int cws = 0;
1118:
1119: for(int run = 0; run < runs.length; run++ )
1120: {
1121: Run current = runs[run];
1122: for(int i = 0; i < current.glyphVector.getNumGlyphs(); i++ )
1123: {
1124: if( wsglyphs[ cws * 2 ] == run && wsglyphs[ cws * 2 + 1 ] == i )
1125: {
1126: cws++;
1127: w += deltaW;
1128: }
1129: Point2D p = current.glyphVector.getGlyphPosition( i );
1130: p.setLocation( p.getX() + w, p.getY() );
1131: current.glyphVector.setGlyphPosition( i, p );
1132: }
1133: }
1134: }
1135:
1136: public TextHitInfo hitTestChar (float x, float y)
1137: {
1138: return hitTestChar(x, y, getNaturalBounds());
1139: }
1140:
1141:
1154: public TextHitInfo hitTestChar (float x, float y, Rectangle2D bounds)
1155: {
1156:
1157: if (isVertical())
1158: {
1159: if (y < bounds.getMinY())
1160: return TextHitInfo.leading(0);
1161: else if (y > bounds.getMaxY())
1162: return TextHitInfo.trailing(getCharacterCount() - 1);
1163: }
1164: else
1165: {
1166: if (x < bounds.getMinX())
1167: return TextHitInfo.leading(0);
1168: else if (x > bounds.getMaxX())
1169: return TextHitInfo.trailing(getCharacterCount() - 1);
1170: }
1171:
1172: TextHitInfo hitInfo = null;
1173: if (isVertical())
1174: {
1175:
1176:
1177:
1178: int numRuns = runs.length;
1179: Run hitRun = null;
1180: for (int i = 0; i < numRuns && hitRun == null; i++)
1181: {
1182: Run run = runs[i];
1183: Rectangle2D lBounds = run.glyphVector.getLogicalBounds();
1184: if (lBounds.getMinY() + run.location <= y
1185: && lBounds.getMaxY() + run.location >= y)
1186: hitRun = run;
1187: }
1188:
1189:
1190: if (hitRun != null)
1191: {
1192: GlyphVector gv = hitRun.glyphVector;
1193: for (int i = hitRun.runStart;
1194: i < hitRun.runEnd && hitInfo == null; i++)
1195: {
1196: int gi = i - hitRun.runStart;
1197: Rectangle2D lBounds = gv.getGlyphLogicalBounds(gi)
1198: .getBounds2D();
1199: if (lBounds.getMinY() + hitRun.location <= y
1200: && lBounds.getMaxY() + hitRun.location >= y)
1201: {
1202:
1203: boolean leading = true;
1204: if (lBounds.getCenterY() + hitRun.location <= y)
1205: leading = false;
1206: hitInfo = leading ? TextHitInfo.leading(i)
1207: : TextHitInfo.trailing(i);
1208: }
1209: }
1210: }
1211: }
1212: else
1213: {
1214:
1215:
1216:
1217: int numRuns = runs.length;
1218: Run hitRun = null;
1219: for (int i = 0; i < numRuns && hitRun == null; i++)
1220: {
1221: Run run = runs[i];
1222: Rectangle2D lBounds = run.glyphVector.getLogicalBounds();
1223: if (lBounds.getMinX() + run.location <= x
1224: && lBounds.getMaxX() + run.location >= x)
1225: hitRun = run;
1226: }
1227:
1228:
1229: if (hitRun != null)
1230: {
1231: GlyphVector gv = hitRun.glyphVector;
1232: for (int i = hitRun.runStart;
1233: i < hitRun.runEnd && hitInfo == null; i++)
1234: {
1235: int gi = i - hitRun.runStart;
1236: Rectangle2D lBounds = gv.getGlyphLogicalBounds(gi)
1237: .getBounds2D();
1238: if (lBounds.getMinX() + hitRun.location <= x
1239: && lBounds.getMaxX() + hitRun.location >= x)
1240: {
1241:
1242: boolean leading = true;
1243: if (lBounds.getCenterX() + hitRun.location <= x)
1244: leading = false;
1245: hitInfo = leading ? TextHitInfo.leading(i)
1246: : TextHitInfo.trailing(i);
1247: }
1248: }
1249: }
1250: }
1251: return hitInfo;
1252: }
1253:
1254: public boolean isLeftToRight ()
1255: {
1256: return leftToRight;
1257: }
1258:
1259: public boolean isVertical ()
1260: {
1261: return false;
1262: }
1263:
1264: public int hashCode ()
1265: {
1266:
1267: if (hash == 0 && runs.length > 0)
1268: {
1269: hash = runs.length;
1270: for (int i = 0; i < runs.length; i++)
1271: hash ^= runs[i].glyphVector.hashCode();
1272: }
1273: return hash;
1274: }
1275:
1276: public String toString ()
1277: {
1278: return "TextLayout [string:"+ new String(string, offset, length)
1279: +" Rendercontext:"+
1280: frc+"]";
1281: }
1282:
1283:
1289: private Rectangle2D getNaturalBounds()
1290: {
1291: if (naturalBounds == null)
1292: naturalBounds = new Rectangle2D.Float(0.0F, -getAscent(), getAdvance(),
1293: getAscent() + getDescent());
1294: return naturalBounds;
1295: }
1296:
1297: private void checkHitInfo(TextHitInfo hit)
1298: {
1299: if (hit == null)
1300: throw new IllegalArgumentException("Null hit info not allowed");
1301: int index = hit.getInsertionIndex();
1302: if (index < 0 || index > length)
1303: throw new IllegalArgumentException("Hit index out of range");
1304: }
1305:
1306: private int hitToCaret(TextHitInfo hit)
1307: {
1308: int index = hit.getCharIndex();
1309: int ret;
1310: if (index < 0)
1311: ret = isLeftToRight() ? 0 : length;
1312: else if (index >= length)
1313: ret = isLeftToRight() ? length : 0;
1314: else
1315: {
1316: ret = logicalToVisual[index];
1317: if (hit.isLeadingEdge() != isCharacterLTR(index))
1318: ret++;
1319: }
1320: return ret;
1321: }
1322:
1323: private TextHitInfo caretToHit(int index)
1324: {
1325: TextHitInfo hit;
1326: if (index == 0 || index == length)
1327: {
1328: if ((index == length) == isLeftToRight())
1329: hit = TextHitInfo.leading(length);
1330: else
1331: hit = TextHitInfo.trailing(-1);
1332: }
1333: else
1334: {
1335: int logical = visualToLogical[index];
1336: boolean leading = isCharacterLTR(logical);
1337: hit = leading ? TextHitInfo.leading(logical)
1338: : TextHitInfo.trailing(logical);
1339: }
1340: return hit;
1341: }
1342:
1343: private boolean isCharacterLTR(int index)
1344: {
1345: byte level = getCharacterLevel(index);
1346: return (level & 1) == 0;
1347: }
1348:
1349:
1357: private Run findRunAtIndex(int index)
1358: {
1359: Run found = null;
1360:
1361: for (int i = 0; i < runs.length && found == null; i++)
1362: {
1363: Run run = runs[i];
1364: if (run.runStart <= index && run.runEnd > index)
1365: found = run;
1366: }
1367: return found;
1368: }
1369:
1370:
1373: private void layoutRuns()
1374: {
1375: float loc = 0.0F;
1376: float lastWidth = 0.0F;
1377: for (int i = 0; i < runs.length; i++)
1378: {
1379: runs[i].location = loc;
1380: Rectangle2D bounds = runs[i].glyphVector.getLogicalBounds();
1381: loc += isVertical() ? bounds.getHeight() : bounds.getWidth();
1382: }
1383: }
1384:
1385:
1388: public static class CaretPolicy
1389: {
1390: public CaretPolicy()
1391: {
1392: }
1393:
1394: public TextHitInfo getStrongCaret(TextHitInfo hit1,
1395: TextHitInfo hit2,
1396: TextLayout layout)
1397: {
1398: byte l1 = layout.getCharacterLevel(hit1.getCharIndex());
1399: byte l2 = layout.getCharacterLevel(hit2.getCharIndex());
1400: TextHitInfo strong;
1401: if (l1 == l2)
1402: {
1403: if (hit2.isLeadingEdge() && ! hit1.isLeadingEdge())
1404: strong = hit2;
1405: else
1406: strong = hit1;
1407: }
1408: else
1409: {
1410: if (l1 < l2)
1411: strong = hit1;
1412: else
1413: strong = hit2;
1414: }
1415: return strong;
1416: }
1417: }
1418: }
1419: