Source for java.awt.font.TextLayout

   1: /* TextLayout.java --
   2:    Copyright (C) 2006  Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package java.awt.font;
  40: 
  41: import gnu.classpath.NotImplementedException;
  42: 
  43: import java.awt.Font;
  44: import java.awt.Graphics2D;
  45: import java.awt.Shape;
  46: import java.awt.geom.AffineTransform;
  47: import java.awt.geom.Rectangle2D;
  48: import java.awt.geom.GeneralPath;
  49: import java.awt.geom.Point2D;
  50: import java.text.CharacterIterator;
  51: import java.text.AttributedCharacterIterator;
  52: import java.text.Bidi;
  53: import java.util.Map;
  54: 
  55: /**
  56:  * @author Sven de Marothy
  57:  */
  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:   /**
  68:    * Start and end character indices of the runs.
  69:    * First index is the run number, second is 0 or 1 for the starting 
  70:    * and ending character index of the run, respectively.
  71:    */
  72:   private int[][] runIndices;
  73: 
  74:   /**
  75:    * Character indices.
  76:    * Fixt index is the glyphvector, second index is the (first) glyph.
  77:    */
  78:   private int[][] charIndices;
  79: 
  80:   /**
  81:    * Base directionality, determined from the first char.
  82:    */
  83:   private boolean leftToRight;
  84: 
  85:   /**
  86:    * Whether this layout contains whitespace or not.
  87:    */
  88:   private boolean hasWhitespace = false;
  89: 
  90:   /**
  91:    * The default caret policy.
  92:    */
  93:   public static final TextLayout.CaretPolicy DEFAULT_CARET_POLICY = new CaretPolicy();
  94: 
  95:   /**
  96:    * Constructs a TextLayout.
  97:    */
  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:     // Get base direction and whitespace info
 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] ) // no empty runs.
 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:     // FIXME: Very rudimentary.
 158:     this(getText(text), getFont(text), frc);
 159:   }
 160: 
 161:   /**
 162:    * Package-private constructor to make a textlayout from an existing one.
 163:    * This is used by TextMeasurer for returning sub-layouts, and it 
 164:    * saves a lot of time in not having to relayout the text.
 165:    */
 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:     // Copy only the relevant parts of the first and last runs.
 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:   /**
 250:    * Scan the character run for the first strongly directional character,
 251:    * which in turn defines the base directionality of the whole layout.
 252:    */
 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:     // Determine if there's whitespace in the thing.
 282:     // Ignore trailing chars.
 283:     int i = string.length() - 1; 
 284:     hasWhitespace = false;
 285:     while( i >= 0 && Character.isWhitespace( string.charAt(i) ) )
 286:       i--;
 287:     // Check the remaining chars
 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:     // Compare all glyph vectors.
 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(); // Hmm? 
 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(); // Hmm? 
 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; // last index in this run to use.
 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; // reset glyph index into run for next run.
 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:     // No trailing whitespace
 565:     if( !Character.isWhitespace( string.charAt( string.length() -1 ) ) )
 566:       return getAdvance();
 567: 
 568:     // Get length of all runs up to the last
 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; // entire last run is whitespace
 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:   /**
 614:    * This is a protected method of a <code>final</code> class, meaning
 615:    * it exists only to taunt you.
 616:    */
 617:   protected void handleJustify (float justificationWidth)
 618:   {
 619:     // We assume that the text has non-trailing whitespace.
 620:     // First get the change in width to insert into the whitespaces.
 621:     double deltaW = justificationWidth - getVisibleAdvance();
 622:     int nglyphs = 0; // # of whitespace chars
 623: 
 624:     // determine last non-whitespace char.
 625:     int lastNWS = string.length() - 1;
 626:     while( Character.isWhitespace( string.charAt( lastNWS ) ) ) lastNWS--;
 627: 
 628:     // locations of the glyphs.
 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:         //          && cindex < lastNWS )
 636:         {
 637:           wsglyphs[ nglyphs * 2 ] = run;
 638:           wsglyphs[ nglyphs * 2 + 1] = i;
 639:           nglyphs++;
 640:         }
 641:     }
 642: 
 643:     deltaW = deltaW / nglyphs; // Change in width per whitespace glyph
 644:     double w = 0;
 645:     int cws = 0;
 646:     // Shift all characters
 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++; // update 'current whitespace'
 653:           w += deltaW; // increment the shift
 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; // FIXME: How do you create a vertical layout?
 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:   /**
 695:    * Inner class describing a caret policy
 696:    */
 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: