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