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:
55:
58: public final class TextLayout implements Cloneable
59: {
60: private GlyphVector[] runs;
61: private Font font;
62: private FontRenderContext frc;
63: private String string;
64: private Rectangle2D boundsCache;
65: private LineMetrics lm;
66:
67:
72: private int[][] runIndices;
73:
74:
78: private int[][] charIndices;
79:
80:
83: private boolean leftToRight;
84:
85:
88: private boolean hasWhitespace = false;
89:
90:
93: public static final TextLayout.CaretPolicy DEFAULT_CARET_POLICY = new CaretPolicy();
94:
95:
98: public TextLayout (String string, Font font, FontRenderContext frc)
99: {
100: this.font = font;
101: this.frc = frc;
102: this.string = string;
103: lm = font.getLineMetrics(string, frc);
104:
105:
106: getStringProperties();
107:
108: if( Bidi.requiresBidi( string.toCharArray(), 0, string.length() ) )
109: {
110: Bidi bidi = new Bidi( string, leftToRight ?
111: Bidi.DIRECTION_LEFT_TO_RIGHT :
112: Bidi.DIRECTION_RIGHT_TO_LEFT );
113: int rc = bidi.getRunCount();
114: byte[] table = new byte[ rc ];
115: for(int i = 0; i < table.length; i++)
116: table[i] = (byte)bidi.getRunLevel(i);
117:
118: runs = new GlyphVector[ rc ];
119: runIndices = new int[rc][2];
120: for(int i = 0; i < runs.length; i++)
121: {
122: runIndices[i][0] = bidi.getRunStart( i );
123: runIndices[i][1] = bidi.getRunLimit( i );
124: if( runIndices[i][0] != runIndices[i][1] )
125: {
126: runs[i] = font.layoutGlyphVector
127: ( frc, string.toCharArray(),
128: runIndices[i][0], runIndices[i][1],
129: ((table[i] & 1) == 0) ? Font.LAYOUT_LEFT_TO_RIGHT :
130: Font.LAYOUT_RIGHT_TO_LEFT );
131: }
132: }
133: Bidi.reorderVisually( table, 0, runs, 0, runs.length );
134: }
135: else
136: {
137: runs = new GlyphVector[ 1 ];
138: runIndices = new int[1][2];
139: runIndices[0][0] = 0;
140: runIndices[0][1] = string.length();
141: runs[ 0 ] = font.layoutGlyphVector( frc, string.toCharArray(),
142: 0, string.length(),
143: leftToRight ?
144: Font.LAYOUT_LEFT_TO_RIGHT :
145: Font.LAYOUT_RIGHT_TO_LEFT );
146: }
147: setCharIndices();
148: }
149:
150: public TextLayout (String string, Map attributes, FontRenderContext frc)
151: {
152: this( string, new Font( attributes ), frc );
153: }
154:
155: public TextLayout (AttributedCharacterIterator text, FontRenderContext frc)
156: {
157:
158: this(getText(text), getFont(text), frc);
159: }
160:
161:
166: TextLayout(TextLayout t, int startIndex, int endIndex)
167: {
168: font = t.font;
169: frc = t.frc;
170: boundsCache = null;
171: lm = t.lm;
172: leftToRight = t.leftToRight;
173:
174: if( endIndex > t.getCharacterCount() )
175: endIndex = t.getCharacterCount();
176: string = t.string.substring( startIndex, endIndex );
177:
178: int startingRun = t.charIndices[startIndex][0];
179: int nRuns = 1 + t.charIndices[endIndex - 1][0] - startingRun;
180: runIndices = new int[ nRuns ][2];
181:
182: runs = new GlyphVector[ nRuns ];
183: for( int i = 0; i < nRuns; i++ )
184: {
185: GlyphVector run = t.runs[ i + startingRun ];
186:
187: int beginGlyphIndex = (i > 0) ? 0 : t.charIndices[startIndex][1];
188: int numEntries = ( i < nRuns - 1) ? run.getNumGlyphs() :
189: 1 + t.charIndices[endIndex - 1][1] - beginGlyphIndex;
190:
191: int[] codes = run.getGlyphCodes(beginGlyphIndex, numEntries, null);
192: runs[ i ] = font.createGlyphVector( frc, codes );
193: runIndices[ i ][0] = t.runIndices[i + startingRun][0] - startIndex;
194: runIndices[ i ][1] = t.runIndices[i + startingRun][1] - startIndex;
195: }
196: runIndices[ nRuns - 1 ][1] = endIndex - 1;
197:
198: setCharIndices();
199: determineWhiteSpace();
200: }
201:
202: private void setCharIndices()
203: {
204: charIndices = new int[ getCharacterCount() ][2];
205: int i = 0;
206: int currentChar = 0;
207: for(int run = 0; run < runs.length; run++)
208: {
209: currentChar = -1;
210: for( int gi = 0; gi < runs[ run ].getNumGlyphs(); gi++)
211: {
212: if( runs[ run ].getGlyphCharIndex( gi ) != currentChar )
213: {
214: charIndices[ i ][0] = run;
215: charIndices[ i ][1] = gi;
216: currentChar = runs[ run ].getGlyphCharIndex( gi );
217: i++;
218: }
219: }
220: }
221: }
222:
223: private static String getText(AttributedCharacterIterator iter)
224: {
225: StringBuffer sb = new StringBuffer();
226: int idx = iter.getIndex();
227: for(char c = iter.first(); c != CharacterIterator.DONE; c = iter.next())
228: sb.append(c);
229: iter.setIndex( idx );
230: return sb.toString();
231: }
232:
233: private static Font getFont(AttributedCharacterIterator iter)
234: {
235: Font f = (Font)iter.getAttribute(TextAttribute.FONT);
236: if( f == null )
237: {
238: int size;
239: Float i = (Float)iter.getAttribute(TextAttribute.SIZE);
240: if( i != null )
241: size = (int)i.floatValue();
242: else
243: size = 14;
244: f = new Font("Dialog", Font.PLAIN, size );
245: }
246: return f;
247: }
248:
249:
253: private void getStringProperties()
254: {
255: boolean gotDirection = false;
256: int i = 0;
257:
258: leftToRight = true;
259: while( i < string.length() && !gotDirection )
260: switch( Character.getDirectionality( string.charAt( i++ ) ) )
261: {
262: case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
263: case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
264: case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
265: gotDirection = true;
266: break;
267:
268: case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
269: case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
270: case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
271: case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
272: leftToRight = false;
273: gotDirection = true;
274: break;
275: }
276: determineWhiteSpace();
277: }
278:
279: private void determineWhiteSpace()
280: {
281:
282:
283: int i = string.length() - 1;
284: hasWhitespace = false;
285: while( i >= 0 && Character.isWhitespace( string.charAt(i) ) )
286: i--;
287:
288: while( i >= 0 )
289: if( Character.isWhitespace( string.charAt(i--) ) )
290: hasWhitespace = true;
291: }
292:
293: protected Object clone ()
294: {
295: return new TextLayout( string, font, frc );
296: }
297:
298: public void draw (Graphics2D g2, float x, float y)
299: {
300: for(int i = 0; i < runs.length; i++)
301: {
302: g2.drawGlyphVector(runs[i], x, y);
303: Rectangle2D r = runs[i].getLogicalBounds();
304: x += r.getWidth();
305: }
306: }
307:
308: public boolean equals (Object obj)
309: {
310: if( !( obj instanceof TextLayout) )
311: return false;
312:
313: return equals( (TextLayout) obj );
314: }
315:
316: public boolean equals (TextLayout tl)
317: {
318: if( runs.length != tl.runs.length )
319: return false;
320:
321: for( int i = 0; i < runs.length; i++ )
322: if( !runs[i].equals( tl.runs[i] ) )
323: return false;
324: return true;
325: }
326:
327: public float getAdvance ()
328: {
329: float totalAdvance = 0f;
330: for(int i = 0; i < runs.length; i++)
331: totalAdvance += runs[i].getLogicalBounds().getWidth();
332: return totalAdvance;
333: }
334:
335: public float getAscent ()
336: {
337: return lm.getAscent();
338: }
339:
340: public byte getBaseline ()
341: {
342: return (byte)lm.getBaselineIndex();
343: }
344:
345: public float[] getBaselineOffsets ()
346: {
347: return lm.getBaselineOffsets();
348: }
349:
350: public Shape getBlackBoxBounds (int firstEndpoint, int secondEndpoint)
351: {
352: if( secondEndpoint - firstEndpoint <= 0 )
353: return new Rectangle2D.Float();
354:
355: if( firstEndpoint < 0 || secondEndpoint > getCharacterCount())
356: return new Rectangle2D.Float();
357:
358: GeneralPath gp = new GeneralPath();
359:
360: int ri = charIndices[ firstEndpoint ][0];
361: int gi = charIndices[ firstEndpoint ][1];
362:
363: double advance = 0;
364:
365: for( int i = 0; i < ri; i++ )
366: advance += runs[i].getLogicalBounds().getWidth();
367:
368: for( int i = ri; i <= charIndices[ secondEndpoint - 1 ][0]; i++ )
369: {
370: int dg;
371: if( i == charIndices[ secondEndpoint - 1 ][0] )
372: dg = charIndices[ secondEndpoint - 1][1];
373: else
374: dg = runs[i].getNumGlyphs() - 1;
375:
376: for( int j = 0; j <= dg; j++ )
377: {
378: Rectangle2D r2 = (runs[i].getGlyphVisualBounds( j )).
379: getBounds2D();
380: Point2D p = runs[i].getGlyphPosition( j );
381: r2.setRect( advance + r2.getX(), r2.getY(),
382: r2.getWidth(), r2.getHeight() );
383: gp.append(r2, false);
384: }
385:
386: advance += runs[i].getLogicalBounds().getWidth();
387: }
388: return gp;
389: }
390:
391: public Rectangle2D getBounds()
392: {
393: if( boundsCache == null )
394: boundsCache = getOutline(new AffineTransform()).getBounds();
395: return boundsCache;
396: }
397:
398: public float[] getCaretInfo (TextHitInfo hit)
399: {
400: return getCaretInfo(hit, getBounds());
401: }
402:
403: public float[] getCaretInfo (TextHitInfo hit, Rectangle2D bounds)
404: throws NotImplementedException
405: {
406: throw new Error ("not implemented");
407: }
408:
409: public Shape getCaretShape (TextHitInfo hit)
410: {
411: return getCaretShape( hit, getBounds() );
412: }
413:
414: public Shape getCaretShape (TextHitInfo hit, Rectangle2D bounds)
415: throws NotImplementedException
416: {
417: throw new Error ("not implemented");
418: }
419:
420: public Shape[] getCaretShapes (int offset)
421: {
422: return getCaretShapes( offset, getBounds() );
423: }
424:
425: public Shape[] getCaretShapes (int offset, Rectangle2D bounds)
426: throws NotImplementedException
427: {
428: throw new Error ("not implemented");
429: }
430:
431: public int getCharacterCount ()
432: {
433: return string.length();
434: }
435:
436: public byte getCharacterLevel (int index)
437: throws NotImplementedException
438: {
439: throw new Error ("not implemented");
440: }
441:
442: public float getDescent ()
443: {
444: return lm.getDescent();
445: }
446:
447: public TextLayout getJustifiedLayout (float justificationWidth)
448: {
449: TextLayout newLayout = (TextLayout)clone();
450:
451: if( hasWhitespace )
452: newLayout.handleJustify( justificationWidth );
453:
454: return newLayout;
455: }
456:
457: public float getLeading ()
458: {
459: return lm.getLeading();
460: }
461:
462: public Shape getLogicalHighlightShape (int firstEndpoint, int secondEndpoint)
463: {
464: return getLogicalHighlightShape( firstEndpoint, secondEndpoint,
465: getBounds() );
466: }
467:
468: public Shape getLogicalHighlightShape (int firstEndpoint, int secondEndpoint,
469: Rectangle2D bounds)
470: {
471: if( secondEndpoint - firstEndpoint <= 0 )
472: return new Rectangle2D.Float();
473:
474: if( firstEndpoint < 0 || secondEndpoint > getCharacterCount())
475: return new Rectangle2D.Float();
476:
477: Rectangle2D r = null;
478: int ri = charIndices[ firstEndpoint ][0];
479: int gi = charIndices[ firstEndpoint ][1];
480:
481: double advance = 0;
482:
483: for( int i = 0; i < ri; i++ )
484: advance += runs[i].getLogicalBounds().getWidth();
485:
486: for( int i = ri; i <= charIndices[ secondEndpoint - 1 ][0]; i++ )
487: {
488: int dg;
489: if( i == charIndices[ secondEndpoint - 1 ][0] )
490: dg = charIndices[ secondEndpoint - 1][1];
491: else
492: dg = runs[i].getNumGlyphs() - 1;
493:
494: for(; gi <= dg; gi++ )
495: {
496: Rectangle2D r2 = (runs[i].getGlyphLogicalBounds( gi )).
497: getBounds2D();
498: if( r == null )
499: r = r2;
500: else
501: r = r.createUnion(r2);
502: }
503: gi = 0;
504:
505: advance += runs[i].getLogicalBounds().getWidth();
506: }
507:
508: return r;
509: }
510:
511: public int[] getLogicalRangesForVisualSelection (TextHitInfo firstEndpoint,
512: TextHitInfo secondEndpoint)
513: throws NotImplementedException
514: {
515: throw new Error ("not implemented");
516: }
517:
518: public TextHitInfo getNextLeftHit (int offset)
519: throws NotImplementedException
520: {
521: throw new Error ("not implemented");
522: }
523:
524: public TextHitInfo getNextLeftHit (TextHitInfo hit)
525: throws NotImplementedException
526: {
527: throw new Error ("not implemented");
528: }
529:
530: public TextHitInfo getNextRightHit (int offset)
531: throws NotImplementedException
532: {
533: throw new Error ("not implemented");
534: }
535:
536: public TextHitInfo getNextRightHit (TextHitInfo hit)
537: throws NotImplementedException
538: {
539: throw new Error ("not implemented");
540: }
541:
542: public Shape getOutline (AffineTransform tx)
543: {
544: float x = 0f;
545: GeneralPath gp = new GeneralPath();
546: for(int i = 0; i < runs.length; i++)
547: {
548: gp.append( runs[i].getOutline( x, 0f ), false );
549: Rectangle2D r = runs[i].getLogicalBounds();
550: x += r.getWidth();
551: }
552: if( tx != null )
553: gp.transform( tx );
554: return gp;
555: }
556:
557: public float getVisibleAdvance ()
558: {
559: float totalAdvance = 0f;
560:
561: if( runs.length <= 0 )
562: return 0f;
563:
564:
565: if( !Character.isWhitespace( string.charAt( string.length() -1 ) ) )
566: return getAdvance();
567:
568:
569: for(int i = 0; i < runs.length - 1; i++)
570: totalAdvance += runs[i].getLogicalBounds().getWidth();
571:
572: int lastRun = runIndices[ runs.length - 1 ][0];
573: int j = string.length() - 1;
574: while( j >= lastRun && Character.isWhitespace( string.charAt( j ) ) ) j--;
575:
576: if( j < lastRun )
577: return totalAdvance;
578:
579: int lastNonWSChar = j - lastRun;
580: j = 0;
581: while( runs[ runs.length - 1 ].getGlyphCharIndex( j )
582: <= lastNonWSChar )
583: {
584: totalAdvance += runs[ runs.length - 1 ].getGlyphLogicalBounds( j ).
585: getBounds2D().getWidth();
586: j ++;
587: }
588:
589: return totalAdvance;
590: }
591:
592: public Shape getVisualHighlightShape (TextHitInfo firstEndpoint,
593: TextHitInfo secondEndpoint)
594: {
595: return getVisualHighlightShape( firstEndpoint, secondEndpoint,
596: getBounds() );
597: }
598:
599: public Shape getVisualHighlightShape (TextHitInfo firstEndpoint,
600: TextHitInfo secondEndpoint,
601: Rectangle2D bounds)
602: throws NotImplementedException
603: {
604: throw new Error ("not implemented");
605: }
606:
607: public TextHitInfo getVisualOtherHit (TextHitInfo hit)
608: throws NotImplementedException
609: {
610: throw new Error ("not implemented");
611: }
612:
613:
617: protected void handleJustify (float justificationWidth)
618: {
619:
620:
621: double deltaW = justificationWidth - getVisibleAdvance();
622: int nglyphs = 0;
623:
624:
625: int lastNWS = string.length() - 1;
626: while( Character.isWhitespace( string.charAt( lastNWS ) ) ) lastNWS--;
627:
628:
629: int[] wsglyphs = new int[string.length() * 10];
630: for(int run = 0; run < runs.length; run++ )
631: for(int i = 0; i < runs[run].getNumGlyphs(); i++ )
632: {
633: int cindex = runIndices[run][0] + runs[run].getGlyphCharIndex( i );
634: if( Character.isWhitespace( string.charAt( cindex ) ) )
635:
636: {
637: wsglyphs[ nglyphs * 2 ] = run;
638: wsglyphs[ nglyphs * 2 + 1] = i;
639: nglyphs++;
640: }
641: }
642:
643: deltaW = deltaW / nglyphs;
644: double w = 0;
645: int cws = 0;
646:
647: for(int run = 0; run < runs.length; run++ )
648: for(int i = 0; i < runs[ run ].getNumGlyphs(); i++ )
649: {
650: if( wsglyphs[ cws * 2 ] == run && wsglyphs[ cws * 2 + 1 ] == i )
651: {
652: cws++;
653: w += deltaW;
654: }
655: Point2D p = runs[ run ].getGlyphPosition( i );
656: p.setLocation( p.getX() + w, p.getY() );
657: runs[ run ].setGlyphPosition( i, p );
658: }
659: }
660:
661: public TextHitInfo hitTestChar (float x, float y)
662: {
663: return hitTestChar(x, y, getBounds());
664: }
665:
666: public TextHitInfo hitTestChar (float x, float y, Rectangle2D bounds)
667: throws NotImplementedException
668: {
669: throw new Error ("not implemented");
670: }
671:
672: public boolean isLeftToRight ()
673: {
674: return leftToRight;
675: }
676:
677: public boolean isVertical ()
678: {
679: return false;
680: }
681:
682: public int hashCode ()
683: throws NotImplementedException
684: {
685: throw new Error ("not implemented");
686: }
687:
688: public String toString ()
689: {
690: return "TextLayout [string:"+string+", Font:"+font+" Rendercontext:"+
691: frc+"]";
692: }
693:
694:
697: public static class CaretPolicy
698: {
699: public CaretPolicy()
700: {
701: }
702:
703: public TextHitInfo getStrongCaret(TextHitInfo hit1,
704: TextHitInfo hit2,
705: TextLayout layout)
706: throws NotImplementedException
707: {
708: throw new Error ("not implemented");
709: }
710: }
711: }
712: