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 java.awt.Font;
  42: import java.awt.Graphics2D;
  43: import java.awt.Shape;
  44: import java.awt.geom.AffineTransform;
  45: import java.awt.geom.Line2D;
  46: import java.awt.geom.Rectangle2D;
  47: import java.awt.geom.GeneralPath;
  48: import java.awt.geom.Point2D;
  49: import java.text.CharacterIterator;
  50: import java.text.AttributedCharacterIterator;
  51: import java.text.Bidi;
  52: import java.util.ArrayList;
  53: import java.util.Map;
  54: 
  55: /**
  56:  * @author Sven de Marothy
  57:  */
  58: public final class TextLayout implements Cloneable
  59: {
  60:   /**
  61:    * Holds the layout data that belongs to one run of characters.
  62:    */
  63:   private class Run
  64:   {
  65:     /**
  66:      * The actual glyph vector.
  67:      */
  68:     GlyphVector glyphVector;
  69: 
  70:     /**
  71:      * The font for this text run.
  72:      */
  73:     Font font;
  74: 
  75:     /**
  76:      * The start of the run.
  77:      */
  78:     int runStart;
  79: 
  80:     /**
  81:      * The end of the run.
  82:      */
  83:     int runEnd;
  84: 
  85:     /**
  86:      * The layout location of the beginning of the run.
  87:      */
  88:     float location;
  89: 
  90:     /**
  91:      * Initializes the Run instance.
  92:      *
  93:      * @param gv the glyph vector
  94:      * @param start the start index of the run
  95:      * @param end the end index of the run
  96:      */
  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:     /**
 106:      * Returns <code>true</code> when this run is left to right,
 107:      * <code>false</code> otherwise.
 108:      *
 109:      * @return <code>true</code> when this run is left to right,
 110:      *         <code>false</code> otherwise
 111:      */
 112:     boolean isLeftToRight()
 113:     {
 114:       return (glyphVector.getLayoutFlags() & GlyphVector.FLAG_RUN_RTL) == 0;
 115:     }
 116:   }
 117: 
 118:   /**
 119:    * The laid out character runs.
 120:    */
 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:   /**
 131:    * The total advance of this text layout. This is cache for maximum
 132:    * performance.
 133:    */
 134:   private float totalAdvance = -1F;
 135:   
 136:   /**
 137:    * The cached natural bounds.
 138:    */
 139:   private Rectangle2D naturalBounds;
 140: 
 141:   /**
 142:    * Character indices.
 143:    * Fixt index is the glyphvector, second index is the (first) glyph.
 144:    */
 145:   private int[][] charIndices;
 146: 
 147:   /**
 148:    * Base directionality, determined from the first char.
 149:    */
 150:   private boolean leftToRight;
 151: 
 152:   /**
 153:    * Whether this layout contains whitespace or not.
 154:    */
 155:   private boolean hasWhitespace = false;
 156: 
 157:   /**
 158:    * The {@link Bidi} object that is used for reordering and by
 159:    * {@link #getCharacterLevel(int)}.
 160:    */
 161:   private Bidi bidi;
 162: 
 163:   /**
 164:    * Mpas the logical position of each individual character in the original
 165:    * string to its visual position.
 166:    */
 167:   private int[] logicalToVisual;
 168: 
 169:   /**
 170:    * Maps visual positions of a character to its logical position
 171:    * in the original string.
 172:    */
 173:   private int[] visualToLogical;
 174: 
 175:   /**
 176:    * The cached hashCode.
 177:    */
 178:   private int hash;
 179: 
 180:   /**
 181:    * The default caret policy.
 182:    */
 183:   public static final TextLayout.CaretPolicy DEFAULT_CARET_POLICY =
 184:     new CaretPolicy();
 185: 
 186:   /**
 187:    * Constructs a TextLayout.
 188:    */
 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:     // Get base direction and whitespace info
 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) // no empty runs.
 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:         // Clean up null runs.
 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:     // FIXME: Very rudimentary.
 257:     this(getText(text), getFont(text), frc);
 258:   }
 259: 
 260:   /**
 261:    * Package-private constructor to make a textlayout from an existing one.
 262:    * This is used by TextMeasurer for returning sub-layouts, and it 
 263:    * saves a lot of time in not having to relayout the text.
 264:    */
 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:     // Copy only the relevant parts of the first and last runs.
 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:   /**
 329:    * Initializes the logicalToVisual and visualToLogial maps.
 330:    */
 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:     // We scan the runs in visual order and set the mappings accordingly.
 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:   /**
 390:    * Scan the character run for the first strongly directional character,
 391:    * which in turn defines the base directionality of the whole layout.
 392:    */
 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:     // Determine if there's whitespace in the thing.
 422:     // Ignore trailing chars.
 423:     int i = offset + length - 1; 
 424:     hasWhitespace = false;
 425:     while( i >= offset && Character.isWhitespace( string[i] ) )
 426:       i--;
 427:     // Check the remaining chars
 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:     // Compare all glyph vectors.
 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(); // Hmm? 
 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:     // For the boundary cases we return the boundary runs.
 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:     // TODO: Handle vertical shapes somehow.
 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:         // Shift x1 and x2 according to the slope.
 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:     // The RI returns a 2-size array even when there's only one
 644:     // shape in it.
 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; // The RI returns null in this seldom case.
 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(); // Hmm? 
 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; // last index in this run to use.
 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; // reset glyph index into run for next run.
 753: 
 754:     advance += gv.getLogicalBounds().getWidth();
 755:       }
 756: 
 757:     return r;
 758:   }
 759: 
 760:   public int[] getLogicalRangesForVisualSelection (TextHitInfo firstEndpoint,
 761:                                                    TextHitInfo secondEndpoint)
 762:   {
 763:     // Check parameters.
 764:     checkHitInfo(firstEndpoint);
 765:     checkHitInfo(secondEndpoint);
 766: 
 767:     // Convert to visual and order correctly.
 768:     int start = hitToCaret(firstEndpoint);
 769:     int end = hitToCaret(secondEndpoint);
 770:     if (start > end)
 771:       {
 772:         // Swap start and end so that end >= start.
 773:         int temp = start;
 774:         start = end;
 775:         end = temp;
 776:       }
 777: 
 778:     // Now walk through the visual indices and mark the included pieces.
 779:     boolean[] include = new boolean[length];
 780:     for (int i = start; i < end; i++)
 781:       {
 782:         include[visualToLogical[i]] = true;
 783:       }
 784: 
 785:     // Count included runs.
 786:     int numRuns = 0;
 787:     boolean in = false;
 788:     for (int i = 0; i < length; i++)
 789:       {
 790:         if (include[i] != in) // At each run in/out point we toggle the in var.
 791:           {
 792:             in = ! in;
 793:             if (in) // At each run start we count up.
 794:               numRuns++;
 795:           }
 796:       }
 797: 
 798:     // Put together the ranges array.
 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:     // If the last run ends at the very end, include that last bit too.
 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:     // No trailing whitespace
 919:     if( !Character.isWhitespace( string[offset + length - 1]) )
 920:       return getAdvance();
 921: 
 922:     // Get length of all runs up to the last
 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; // entire last run is whitespace
 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:     // Append left (top) bounds to selection if necessary.
 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:     // Append right (bottom) bounds if necessary.
 971:     if (c1 == length || c2 == length)
 972:       {
 973:         path.append(right(bounds), false);
 974:       }
 975:     return path.getBounds2D();
 976:   }
 977: 
 978:   /**
 979:    * Returns the shape that makes up the left (top) edge of this text layout.
 980:    *
 981:    * @param b the bounds
 982:    *
 983:    * @return the shape that makes up the left (top) edge of this text layout
 984:    */
 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:   /**
1005:    * Returns the shape that makes up the right (bottom) edge of this text
1006:    * layout.
1007:    *
1008:    * @param b the bounds
1009:    *
1010:    * @return the shape that makes up the right (bottom) edge of this text
1011:    *         layout
1012:    */
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:         // Boundary case.
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); // LTR.
1050:         else
1051:           leading = ! isCharacterLTR(index); // RTL.
1052:       }
1053:     else
1054:       {
1055:         // Normal case.
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:   /**
1083:    * This is a protected method of a <code>final</code> class, meaning
1084:    * it exists only to taunt you.
1085:    */
1086:   protected void handleJustify (float justificationWidth)
1087:   {
1088:     // We assume that the text has non-trailing whitespace.
1089:     // First get the change in width to insert into the whitespaces.
1090:     double deltaW = justificationWidth - getVisibleAdvance();
1091:     int nglyphs = 0; // # of whitespace chars
1092: 
1093:     // determine last non-whitespace char.
1094:     int lastNWS = offset + length - 1;
1095:     while( Character.isWhitespace( string[lastNWS] ) ) lastNWS--;
1096: 
1097:     // locations of the glyphs.
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:         //          && cindex < lastNWS )
1108:         {
1109:           wsglyphs[ nglyphs * 2 ] = run;
1110:           wsglyphs[ nglyphs * 2 + 1] = i;
1111:           nglyphs++;
1112:         }
1113:     }
1114:       }
1115:     deltaW = deltaW / nglyphs; // Change in width per whitespace glyph
1116:     double w = 0;
1117:     int cws = 0;
1118:     // Shift all characters
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++; // update 'current whitespace'
1127:                 w += deltaW; // increment the shift
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:   /**
1142:    * Finds the character hit at the specified point. This 'clips' this
1143:    * text layout against the specified <code>bounds</code> rectangle. That
1144:    * means that in the case where a point is outside these bounds, this method
1145:    * returns the leading edge of the first character or the trailing edge of
1146:    * the last character.
1147:    *
1148:    * @param x the X location to test
1149:    * @param y the Y location to test
1150:    * @param bounds the bounds to test against
1151:    *
1152:    * @return the character hit at the specified point
1153:    */
1154:   public TextHitInfo hitTestChar (float x, float y, Rectangle2D bounds)
1155:   {
1156:     // Check bounds.
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:         // Search for the run at the location.
1176:         // TODO: Perform binary search for maximum efficiency. However, we
1177:         // need the run location laid out statically to do that.
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:         // Now we have (hopefully) found a run that hits. Now find the
1189:         // right character.
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:                     // Found hit. Now check if we are leading or trailing.
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:         // Search for the run at the location.
1215:         // TODO: Perform binary search for maximum efficiency. However, we
1216:         // need the run location laid out statically to do that.
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:         // Now we have (hopefully) found a run that hits. Now find the
1228:         // right character.
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:                     // Found hit. Now check if we are leading or trailing.
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; // FIXME: How do you create a vertical layout?
1262:   }
1263: 
1264:   public int hashCode ()
1265:   {
1266:     // This is implemented in sync to equals().
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:   /**
1284:    * Returns the natural bounds of that text layout. This is made up
1285:    * of the ascent plus descent and the text advance.
1286:    *
1287:    * @return the natural bounds of that text layout
1288:    */
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); // LTR.
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:   /**
1350:    * Finds the run that holds the specified (logical) character index. This
1351:    * returns <code>null</code> when the index is not inside the range.
1352:    *
1353:    * @param index the index of the character to find
1354:    *
1355:    * @return the run that holds the specified character
1356:    */
1357:   private Run findRunAtIndex(int index)
1358:   {
1359:     Run found = null;
1360:     // TODO: Can we do better than linear searching here?
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:   /**
1371:    * Computes the layout locations for each run.
1372:    */
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:   /**
1386:    * Inner class describing a caret policy
1387:    */
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: