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