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