1 /*
   2  * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /*
  27  * (C) Copyright Taligent, Inc. 1996 - 1997, All Rights Reserved
  28  * (C) Copyright IBM Corp. 1996-2003, All Rights Reserved
  29  *
  30  * The original version of this source code and documentation is
  31  * copyrighted and owned by Taligent, Inc., a wholly-owned subsidiary
  32  * of IBM. These materials are provided under terms of a License
  33  * Agreement between Taligent and Sun. This technology is protected
  34  * by multiple US and International patents.
  35  *
  36  * This notice and attribution to Taligent may not be removed.
  37  * Taligent is a registered trademark of Taligent, Inc.
  38  *
  39  */
  40 
  41 package java.awt.font;
  42 
  43 import java.awt.Color;
  44 import java.awt.Font;
  45 import java.awt.Graphics2D;
  46 import java.awt.Rectangle;
  47 import java.awt.Shape;
  48 import java.awt.font.NumericShaper;
  49 import java.awt.font.TextLine.TextLineMetrics;
  50 import java.awt.geom.AffineTransform;
  51 import java.awt.geom.GeneralPath;
  52 import java.awt.geom.NoninvertibleTransformException;
  53 import java.awt.geom.Point2D;
  54 import java.awt.geom.Rectangle2D;
  55 import java.text.AttributedString;
  56 import java.text.AttributedCharacterIterator;
  57 import java.text.AttributedCharacterIterator.Attribute;
  58 import java.text.CharacterIterator;
  59 import java.util.Map;
  60 import java.util.HashMap;
  61 import java.util.Hashtable;
  62 import sun.font.AttributeValues;
  63 import sun.font.CodePointIterator;
  64 import sun.font.CoreMetrics;
  65 import sun.font.Decoration;
  66 import sun.font.FontLineMetrics;
  67 import sun.font.FontResolver;
  68 import sun.font.GraphicComponent;
  69 import sun.font.LayoutPathImpl;

  70 
  71 /**
  72  *
  73  * <code>TextLayout</code> is an immutable graphical representation of styled
  74  * character data.
  75  * <p>
  76  * It provides the following capabilities:
  77  * <ul>
  78  * <li>implicit bidirectional analysis and reordering,
  79  * <li>cursor positioning and movement, including split cursors for
  80  * mixed directional text,
  81  * <li>highlighting, including both logical and visual highlighting
  82  * for mixed directional text,
  83  * <li>multiple baselines (roman, hanging, and centered),
  84  * <li>hit testing,
  85  * <li>justification,
  86  * <li>default font substitution,
  87  * <li>metric information such as ascent, descent, and advance, and
  88  * <li>rendering
  89  * </ul>
  90  * <p>
  91  * A <code>TextLayout</code> object can be rendered using
  92  * its <code>draw</code> method.
  93  * <p>
  94  * <code>TextLayout</code> can be constructed either directly or through
  95  * the use of a {@link LineBreakMeasurer}.  When constructed directly, the
  96  * source text represents a single paragraph.  <code>LineBreakMeasurer</code>
  97  * allows styled text to be broken into lines that fit within a particular
  98  * width.  See the <code>LineBreakMeasurer</code> documentation for more
  99  * information.
 100  * <p>
 101  * <code>TextLayout</code> construction logically proceeds as follows:
 102  * <ul>
 103  * <li>paragraph attributes are extracted and examined,
 104  * <li>text is analyzed for bidirectional reordering, and reordering
 105  * information is computed if needed,
 106  * <li>text is segmented into style runs
 107  * <li>fonts are chosen for style runs, first by using a font if the
 108  * attribute {@link TextAttribute#FONT} is present, otherwise by computing
 109  * a default font using the attributes that have been defined
 110  * <li>if text is on multiple baselines, the runs or subruns are further
 111  * broken into subruns sharing a common baseline,
 112  * <li>glyphvectors are generated for each run using the chosen font,
 113  * <li>final bidirectional reordering is performed on the glyphvectors
 114  * </ul>
 115  * <p>
 116  * All graphical information returned from a <code>TextLayout</code>
 117  * object's methods is relative to the origin of the
 118  * <code>TextLayout</code>, which is the intersection of the
 119  * <code>TextLayout</code> object's baseline with its left edge.  Also,
 120  * coordinates passed into a <code>TextLayout</code> object's methods
 121  * are assumed to be relative to the <code>TextLayout</code> object's
 122  * origin.  Clients usually need to translate between a
 123  * <code>TextLayout</code> object's coordinate system and the coordinate
 124  * system in another object (such as a
 125  * {@link java.awt.Graphics Graphics} object).
 126  * <p>
 127  * <code>TextLayout</code> objects are constructed from styled text,
 128  * but they do not retain a reference to their source text.  Thus,
 129  * changes in the text previously used to generate a <code>TextLayout</code>
 130  * do not affect the <code>TextLayout</code>.
 131  * <p>
 132  * Three methods on a <code>TextLayout</code> object
 133  * (<code>getNextRightHit</code>, <code>getNextLeftHit</code>, and
 134  * <code>hitTestChar</code>) return instances of {@link TextHitInfo}.
 135  * The offsets contained in these <code>TextHitInfo</code> objects
 136  * are relative to the start of the <code>TextLayout</code>, <b>not</b>
 137  * to the text used to create the <code>TextLayout</code>.  Similarly,
 138  * <code>TextLayout</code> methods that accept <code>TextHitInfo</code>
 139  * instances as parameters expect the <code>TextHitInfo</code> object's
 140  * offsets to be relative to the <code>TextLayout</code>, not to any
 141  * underlying text storage model.
 142  * <p>
 143  * <strong>Examples</strong>:<p>
 144  * Constructing and drawing a <code>TextLayout</code> and its bounding
 145  * rectangle:
 146  * <blockquote><pre>
 147  *   Graphics2D g = ...;
 148  *   Point2D loc = ...;
 149  *   Font font = Font.getFont("Helvetica-bold-italic");
 150  *   FontRenderContext frc = g.getFontRenderContext();
 151  *   TextLayout layout = new TextLayout("This is a string", font, frc);
 152  *   layout.draw(g, (float)loc.getX(), (float)loc.getY());
 153  *
 154  *   Rectangle2D bounds = layout.getBounds();
 155  *   bounds.setRect(bounds.getX()+loc.getX(),
 156  *                  bounds.getY()+loc.getY(),
 157  *                  bounds.getWidth(),
 158  *                  bounds.getHeight());
 159  *   g.draw(bounds);
 160  * </pre>
 161  * </blockquote>
 162  * <p>
 163  * Hit-testing a <code>TextLayout</code> (determining which character is at
 164  * a particular graphical location):
 165  * <blockquote><pre>
 166  *   Point2D click = ...;
 167  *   TextHitInfo hit = layout.hitTestChar(
 168  *                         (float) (click.getX() - loc.getX()),
 169  *                         (float) (click.getY() - loc.getY()));
 170  * </pre>
 171  * </blockquote>
 172  * <p>
 173  * Responding to a right-arrow key press:
 174  * <blockquote><pre>
 175  *   int insertionIndex = ...;
 176  *   TextHitInfo next = layout.getNextRightHit(insertionIndex);
 177  *   if (next != null) {
 178  *       // translate graphics to origin of layout on screen
 179  *       g.translate(loc.getX(), loc.getY());
 180  *       Shape[] carets = layout.getCaretShapes(next.getInsertionIndex());
 181  *       g.draw(carets[0]);
 182  *       if (carets[1] != null) {
 183  *           g.draw(carets[1]);
 184  *       }
 185  *   }
 186  * </pre></blockquote>
 187  * <p>
 188  * Drawing a selection range corresponding to a substring in the source text.
 189  * The selected area may not be visually contiguous:
 190  * <blockquote><pre>
 191  *   // selStart, selLimit should be relative to the layout,
 192  *   // not to the source text
 193  *
 194  *   int selStart = ..., selLimit = ...;
 195  *   Color selectionColor = ...;
 196  *   Shape selection = layout.getLogicalHighlightShape(selStart, selLimit);
 197  *   // selection may consist of disjoint areas
 198  *   // graphics is assumed to be tranlated to origin of layout
 199  *   g.setColor(selectionColor);
 200  *   g.fill(selection);
 201  * </pre></blockquote>
 202  * <p>
 203  * Drawing a visually contiguous selection range.  The selection range may
 204  * correspond to more than one substring in the source text.  The ranges of
 205  * the corresponding source text substrings can be obtained with
 206  * <code>getLogicalRangesForVisualSelection()</code>:
 207  * <blockquote><pre>
 208  *   TextHitInfo selStart = ..., selLimit = ...;
 209  *   Shape selection = layout.getVisualHighlightShape(selStart, selLimit);
 210  *   g.setColor(selectionColor);
 211  *   g.fill(selection);
 212  *   int[] ranges = getLogicalRangesForVisualSelection(selStart, selLimit);
 213  *   // ranges[0], ranges[1] is the first selection range,
 214  *   // ranges[2], ranges[3] is the second selection range, etc.
 215  * </pre></blockquote>
 216  * <p>
 217  * Note: Font rotations can cause text baselines to be rotated, and
 218  * multiple runs with different rotations can cause the baseline to
 219  * bend or zig-zag.  In order to account for this (rare) possibility,
 220  * some APIs are specified to return metrics and take parameters 'in
 221  * baseline-relative coordinates' (e.g. ascent, advance), and others
 222  * are in 'in standard coordinates' (e.g. getBounds).  Values in
 223  * baseline-relative coordinates map the 'x' coordinate to the
 224  * distance along the baseline, (positive x is forward along the
 225  * baseline), and the 'y' coordinate to a distance along the
 226  * perpendicular to the baseline at 'x' (positive y is 90 degrees
 227  * clockwise from the baseline vector).  Values in standard
 228  * coordinates are measured along the x and y axes, with 0,0 at the
 229  * origin of the TextLayout.  Documentation for each relevant API
 230  * indicates what values are in what coordinate system.  In general,
 231  * measurement-related APIs are in baseline-relative coordinates,
 232  * while display-related APIs are in standard coordinates.
 233  *
 234  * @see LineBreakMeasurer
 235  * @see TextAttribute
 236  * @see TextHitInfo
 237  * @see LayoutPath
 238  */
 239 public final class TextLayout implements Cloneable {
 240 
 241     private int characterCount;
 242     private boolean isVerticalLine = false;
 243     private byte baseline;
 244     private float[] baselineOffsets;  // why have these ?
 245     private TextLine textLine;
 246 
 247     // cached values computed from GlyphSets and set info:
 248     // all are recomputed from scratch in buildCache()
 249     private TextLine.TextLineMetrics lineMetrics = null;
 250     private float visibleAdvance;
 251     private int hashCodeCache;
 252 
 253     /*
 254      * TextLayouts are supposedly immutable.  If you mutate a TextLayout under
 255      * the covers (like the justification code does) you'll need to set this
 256      * back to false.  Could be replaced with textLine != null <--> cacheIsValid.
 257      */
 258     private boolean cacheIsValid = false;
 259 
 260 
 261     // This value is obtained from an attribute, and constrained to the
 262     // interval [0,1].  If 0, the layout cannot be justified.
 263     private float justifyRatio;
 264 
 265     // If a layout is produced by justification, then that layout
 266     // cannot be justified.  To enforce this constraint the
 267     // justifyRatio of the justified layout is set to this value.
 268     private static final float ALREADY_JUSTIFIED = -53.9f;
 269 
 270     // dx and dy specify the distance between the TextLayout's origin
 271     // and the origin of the leftmost GlyphSet (TextLayoutComponent,
 272     // actually).  They were used for hanging punctuation support,
 273     // which is no longer implemented.  Currently they are both always 0,
 274     // and TextLayout is not guaranteed to work with non-zero dx, dy
 275     // values right now.  They were left in as an aide and reminder to
 276     // anyone who implements hanging punctuation or other similar stuff.
 277     // They are static now so they don't take up space in TextLayout
 278     // instances.
 279     private static float dx;
 280     private static float dy;
 281 
 282     /*
 283      * Natural bounds is used internally.  It is built on demand in
 284      * getNaturalBounds.
 285      */
 286     private Rectangle2D naturalBounds = null;
 287 
 288     /*
 289      * boundsRect encloses all of the bits this TextLayout can draw.  It
 290      * is build on demand in getBounds.
 291      */
 292     private Rectangle2D boundsRect = null;
 293 
 294     /*
 295      * flag to supress/allow carets inside of ligatures when hit testing or
 296      * arrow-keying
 297      */
 298     private boolean caretsInLigaturesAreAllowed = false;
 299 
 300     /**
 301      * Defines a policy for determining the strong caret location.
 302      * This class contains one method, <code>getStrongCaret</code>, which
 303      * is used to specify the policy that determines the strong caret in
 304      * dual-caret text.  The strong caret is used to move the caret to the
 305      * left or right. Instances of this class can be passed to
 306      * <code>getCaretShapes</code>, <code>getNextLeftHit</code> and
 307      * <code>getNextRightHit</code> to customize strong caret
 308      * selection.
 309      * <p>
 310      * To specify alternate caret policies, subclass <code>CaretPolicy</code>
 311      * and override <code>getStrongCaret</code>.  <code>getStrongCaret</code>
 312      * should inspect the two <code>TextHitInfo</code> arguments and choose
 313      * one of them as the strong caret.
 314      * <p>
 315      * Most clients do not need to use this class.
 316      */
 317     public static class CaretPolicy {
 318 
 319         /**
 320          * Constructs a <code>CaretPolicy</code>.
 321          */
 322          public CaretPolicy() {
 323          }
 324 
 325         /**
 326          * Chooses one of the specified <code>TextHitInfo</code> instances as
 327          * a strong caret in the specified <code>TextLayout</code>.
 328          * @param hit1 a valid hit in <code>layout</code>
 329          * @param hit2 a valid hit in <code>layout</code>
 330          * @param layout the <code>TextLayout</code> in which
 331          *        <code>hit1</code> and <code>hit2</code> are used
 332          * @return <code>hit1</code> or <code>hit2</code>
 333          *        (or an equivalent <code>TextHitInfo</code>), indicating the
 334          *        strong caret.
 335          */
 336         public TextHitInfo getStrongCaret(TextHitInfo hit1,
 337                                           TextHitInfo hit2,
 338                                           TextLayout layout) {
 339 
 340             // default implementation just calls private method on layout
 341             return layout.getStrongHit(hit1, hit2);
 342         }
 343     }
 344 
 345     /**
 346      * This <code>CaretPolicy</code> is used when a policy is not specified
 347      * by the client.  With this policy, a hit on a character whose direction
 348      * is the same as the line direction is stronger than a hit on a
 349      * counterdirectional character.  If the characters' directions are
 350      * the same, a hit on the leading edge of a character is stronger
 351      * than a hit on the trailing edge of a character.
 352      */
 353     public static final CaretPolicy DEFAULT_CARET_POLICY = new CaretPolicy();
 354 
 355     /**
 356      * Constructs a <code>TextLayout</code> from a <code>String</code>
 357      * and a {@link Font}.  All the text is styled using the specified
 358      * <code>Font</code>.
 359      * <p>
 360      * The <code>String</code> must specify a single paragraph of text,
 361      * because an entire paragraph is required for the bidirectional
 362      * algorithm.
 363      * @param string the text to display
 364      * @param font a <code>Font</code> used to style the text
 365      * @param frc contains information about a graphics device which is needed
 366      *       to measure the text correctly.
 367      *       Text measurements can vary slightly depending on the
 368      *       device resolution, and attributes such as antialiasing.  This
 369      *       parameter does not specify a translation between the
 370      *       <code>TextLayout</code> and user space.
 371      */
 372     public TextLayout(String string, Font font, FontRenderContext frc) {
 373 
 374         if (font == null) {
 375             throw new IllegalArgumentException("Null font passed to TextLayout constructor.");
 376         }
 377 
 378         if (string == null) {
 379             throw new IllegalArgumentException("Null string passed to TextLayout constructor.");
 380         }
 381 
 382         if (string.length() == 0) {
 383             throw new IllegalArgumentException("Zero length string passed to TextLayout constructor.");
 384         }
 385 
 386         Map<? extends Attribute, ?> attributes = null;
 387         if (font.hasLayoutAttributes()) {
 388             attributes = font.getAttributes();
 389         }
 390 
 391         char[] text = string.toCharArray();
 392         if (sameBaselineUpTo(font, text, 0, text.length) == text.length) {
 393             fastInit(text, font, attributes, frc);
 394         } else {
 395             AttributedString as = attributes == null
 396                 ? new AttributedString(string)
 397                 : new AttributedString(string, attributes);
 398             as.addAttribute(TextAttribute.FONT, font);
 399             standardInit(as.getIterator(), text, frc);
 400         }
 401     }
 402 
 403     /**
 404      * Constructs a <code>TextLayout</code> from a <code>String</code>
 405      * and an attribute set.
 406      * <p>
 407      * All the text is styled using the provided attributes.
 408      * <p>
 409      * <code>string</code> must specify a single paragraph of text because an
 410      * entire paragraph is required for the bidirectional algorithm.
 411      * @param string the text to display
 412      * @param attributes the attributes used to style the text
 413      * @param frc contains information about a graphics device which is needed
 414      *       to measure the text correctly.
 415      *       Text measurements can vary slightly depending on the
 416      *       device resolution, and attributes such as antialiasing.  This
 417      *       parameter does not specify a translation between the
 418      *       <code>TextLayout</code> and user space.
 419      */
 420     public TextLayout(String string, Map<? extends Attribute,?> attributes,
 421                       FontRenderContext frc)
 422     {
 423         if (string == null) {
 424             throw new IllegalArgumentException("Null string passed to TextLayout constructor.");
 425         }
 426 
 427         if (attributes == null) {
 428             throw new IllegalArgumentException("Null map passed to TextLayout constructor.");
 429         }
 430 
 431         if (string.length() == 0) {
 432             throw new IllegalArgumentException("Zero length string passed to TextLayout constructor.");
 433         }
 434 
 435         char[] text = string.toCharArray();
 436         Font font = singleFont(text, 0, text.length, attributes);
 437         if (font != null) {
 438             fastInit(text, font, attributes, frc);
 439         } else {
 440             AttributedString as = new AttributedString(string, attributes);
 441             standardInit(as.getIterator(), text, frc);
 442         }
 443     }
 444 
 445     /*
 446      * Determines a font for the attributes, and if a single font can render
 447      * all the text on one baseline, return it, otherwise null.  If the
 448      * attributes specify a font, assume it can display all the text without
 449      * checking.
 450      * If the AttributeSet contains an embedded graphic, return null.
 451      */
 452     private static Font singleFont(char[] text,
 453                                    int start,
 454                                    int limit,
 455                                    Map<? extends Attribute, ?> attributes) {
 456 
 457         if (attributes.get(TextAttribute.CHAR_REPLACEMENT) != null) {
 458             return null;
 459         }
 460 
 461         Font font = null;
 462         try {
 463             font = (Font)attributes.get(TextAttribute.FONT);
 464         }
 465         catch (ClassCastException e) {
 466         }
 467         if (font == null) {
 468             if (attributes.get(TextAttribute.FAMILY) != null) {
 469                 font = Font.getFont(attributes);
 470                 if (font.canDisplayUpTo(text, start, limit) != -1) {
 471                     return null;
 472                 }
 473             } else {
 474                 FontResolver resolver = FontResolver.getInstance();
 475                 CodePointIterator iter = CodePointIterator.create(text, start, limit);
 476                 int fontIndex = resolver.nextFontRunIndex(iter);
 477                 if (iter.charIndex() == limit) {
 478                     font = resolver.getFont(fontIndex, attributes);
 479                 }
 480             }
 481         }
 482 
 483         if (sameBaselineUpTo(font, text, start, limit) != limit) {
 484             return null;
 485         }
 486 
 487         return font;
 488     }
 489 
 490     /**
 491      * Constructs a <code>TextLayout</code> from an iterator over styled text.
 492      * <p>
 493      * The iterator must specify a single paragraph of text because an
 494      * entire paragraph is required for the bidirectional
 495      * algorithm.
 496      * @param text the styled text to display
 497      * @param frc contains information about a graphics device which is needed
 498      *       to measure the text correctly.
 499      *       Text measurements can vary slightly depending on the
 500      *       device resolution, and attributes such as antialiasing.  This
 501      *       parameter does not specify a translation between the
 502      *       <code>TextLayout</code> and user space.
 503      */
 504     public TextLayout(AttributedCharacterIterator text, FontRenderContext frc) {
 505 
 506         if (text == null) {
 507             throw new IllegalArgumentException("Null iterator passed to TextLayout constructor.");
 508         }
 509 
 510         int start = text.getBeginIndex();
 511         int limit = text.getEndIndex();
 512         if (start == limit) {
 513             throw new IllegalArgumentException("Zero length iterator passed to TextLayout constructor.");
 514         }
 515 
 516         int len = limit - start;
 517         text.first();
 518         char[] chars = new char[len];
 519         int n = 0;
 520         for (char c = text.first();
 521              c != CharacterIterator.DONE;
 522              c = text.next())
 523         {
 524             chars[n++] = c;
 525         }
 526 
 527         text.first();
 528         if (text.getRunLimit() == limit) {
 529 
 530             Map<? extends Attribute, ?> attributes = text.getAttributes();
 531             Font font = singleFont(chars, 0, len, attributes);
 532             if (font != null) {
 533                 fastInit(chars, font, attributes, frc);
 534                 return;
 535             }
 536         }
 537 
 538         standardInit(text, chars, frc);
 539     }
 540 
 541     /**
 542      * Creates a <code>TextLayout</code> from a {@link TextLine} and
 543      * some paragraph data.  This method is used by {@link TextMeasurer}.
 544      * @param textLine the line measurement attributes to apply to the
 545      *       the resulting <code>TextLayout</code>
 546      * @param baseline the baseline of the text
 547      * @param baselineOffsets the baseline offsets for this
 548      * <code>TextLayout</code>.  This should already be normalized to
 549      * <code>baseline</code>
 550      * @param justifyRatio <code>0</code> if the <code>TextLayout</code>
 551      *     cannot be justified; <code>1</code> otherwise.
 552      */
 553     TextLayout(TextLine textLine,
 554                byte baseline,
 555                float[] baselineOffsets,
 556                float justifyRatio) {
 557 
 558         this.characterCount = textLine.characterCount();
 559         this.baseline = baseline;
 560         this.baselineOffsets = baselineOffsets;
 561         this.textLine = textLine;
 562         this.justifyRatio = justifyRatio;
 563     }
 564 
 565     /**
 566      * Initialize the paragraph-specific data.
 567      */
 568     private void paragraphInit(byte aBaseline, CoreMetrics lm,
 569                                Map<? extends Attribute, ?> paragraphAttrs,
 570                                char[] text) {
 571 
 572         baseline = aBaseline;
 573 
 574         // normalize to current baseline
 575         baselineOffsets = TextLine.getNormalizedOffsets(lm.baselineOffsets, baseline);
 576 
 577         justifyRatio = AttributeValues.getJustification(paragraphAttrs);
 578         NumericShaper shaper = AttributeValues.getNumericShaping(paragraphAttrs);
 579         if (shaper != null) {
 580             shaper.shape(text, 0, text.length);
 581         }
 582     }
 583 
 584     /*
 585      * the fast init generates a single glyph set.  This requires:
 586      * all one style
 587      * all renderable by one font (ie no embedded graphics)
 588      * all on one baseline
 589      */
 590     private void fastInit(char[] chars, Font font,
 591                           Map<? extends Attribute, ?> attrs,
 592                           FontRenderContext frc) {
 593 
 594         // Object vf = attrs.get(TextAttribute.ORIENTATION);
 595         // isVerticalLine = TextAttribute.ORIENTATION_VERTICAL.equals(vf);
 596         isVerticalLine = false;
 597 
 598         LineMetrics lm = font.getLineMetrics(chars, 0, chars.length, frc);
 599         CoreMetrics cm = CoreMetrics.get(lm);
 600         byte glyphBaseline = (byte) cm.baselineIndex;
 601 
 602         if (attrs == null) {
 603             baseline = glyphBaseline;
 604             baselineOffsets = cm.baselineOffsets;
 605             justifyRatio = 1.0f;
 606         } else {
 607             paragraphInit(glyphBaseline, cm, attrs, chars);
 608         }
 609 
 610         characterCount = chars.length;
 611 
 612         textLine = TextLine.fastCreateTextLine(frc, chars, font, cm, attrs);
 613     }
 614 
 615     /*
 616      * the standard init generates multiple glyph sets based on style,
 617      * renderable, and baseline runs.
 618      * @param chars the text in the iterator, extracted into a char array
 619      */
 620     private void standardInit(AttributedCharacterIterator text, char[] chars, FontRenderContext frc) {
 621 
 622         characterCount = chars.length;
 623 
 624         // set paragraph attributes
 625         {
 626             // If there's an embedded graphic at the start of the
 627             // paragraph, look for the first non-graphic character
 628             // and use it and its font to initialize the paragraph.
 629             // If not, use the first graphic to initialize.
 630 
 631             Map<? extends Attribute, ?> paragraphAttrs = text.getAttributes();
 632 
 633             boolean haveFont = TextLine.advanceToFirstFont(text);
 634 
 635             if (haveFont) {
 636                 Font defaultFont = TextLine.getFontAtCurrentPos(text);
 637                 int charsStart = text.getIndex() - text.getBeginIndex();
 638                 LineMetrics lm = defaultFont.getLineMetrics(chars, charsStart, charsStart+1, frc);
 639                 CoreMetrics cm = CoreMetrics.get(lm);
 640                 paragraphInit((byte)cm.baselineIndex, cm, paragraphAttrs, chars);
 641             }
 642             else {
 643                 // hmmm what to do here?  Just try to supply reasonable
 644                 // values I guess.
 645 
 646                 GraphicAttribute graphic = (GraphicAttribute)
 647                                 paragraphAttrs.get(TextAttribute.CHAR_REPLACEMENT);
 648                 byte defaultBaseline = getBaselineFromGraphic(graphic);
 649                 CoreMetrics cm = GraphicComponent.createCoreMetrics(graphic);
 650                 paragraphInit(defaultBaseline, cm, paragraphAttrs, chars);
 651             }
 652         }
 653 
 654         textLine = TextLine.standardCreateTextLine(frc, text, chars, baselineOffsets);
 655     }
 656 
 657     /*
 658      * A utility to rebuild the ascent/descent/leading/advance cache.
 659      * You'll need to call this if you clone and mutate (like justification,
 660      * editing methods do)
 661      */
 662     private void ensureCache() {
 663         if (!cacheIsValid) {
 664             buildCache();
 665         }
 666     }
 667 
 668     private void buildCache() {
 669         lineMetrics = textLine.getMetrics();
 670 
 671         // compute visibleAdvance
 672         if (textLine.isDirectionLTR()) {
 673 
 674             int lastNonSpace = characterCount-1;
 675             while (lastNonSpace != -1) {
 676                 int logIndex = textLine.visualToLogical(lastNonSpace);
 677                 if (!textLine.isCharSpace(logIndex)) {
 678                     break;
 679                 }
 680                 else {
 681                     --lastNonSpace;
 682                 }
 683             }
 684             if (lastNonSpace == characterCount-1) {
 685                 visibleAdvance = lineMetrics.advance;
 686             }
 687             else if (lastNonSpace == -1) {
 688                 visibleAdvance = 0;
 689             }
 690             else {
 691                 int logIndex = textLine.visualToLogical(lastNonSpace);
 692                 visibleAdvance = textLine.getCharLinePosition(logIndex)
 693                                         + textLine.getCharAdvance(logIndex);
 694             }
 695         }
 696         else {
 697 
 698             int leftmostNonSpace = 0;
 699             while (leftmostNonSpace != characterCount) {
 700                 int logIndex = textLine.visualToLogical(leftmostNonSpace);
 701                 if (!textLine.isCharSpace(logIndex)) {
 702                     break;
 703                 }
 704                 else {
 705                     ++leftmostNonSpace;
 706                 }
 707             }
 708             if (leftmostNonSpace == characterCount) {
 709                 visibleAdvance = 0;
 710             }
 711             else if (leftmostNonSpace == 0) {
 712                 visibleAdvance = lineMetrics.advance;
 713             }
 714             else {
 715                 int logIndex = textLine.visualToLogical(leftmostNonSpace);
 716                 float pos = textLine.getCharLinePosition(logIndex);
 717                 visibleAdvance = lineMetrics.advance - pos;
 718             }
 719         }
 720 
 721         // naturalBounds, boundsRect will be generated on demand
 722         naturalBounds = null;
 723         boundsRect = null;
 724 
 725         // hashCode will be regenerated on demand
 726         hashCodeCache = 0;
 727 
 728         cacheIsValid = true;
 729     }
 730 
 731     /**
 732      * The 'natural bounds' encloses all the carets the layout can draw.
 733      *
 734      */
 735     private Rectangle2D getNaturalBounds() {
 736         ensureCache();
 737 
 738         if (naturalBounds == null) {
 739             naturalBounds = textLine.getItalicBounds();
 740         }
 741 
 742         return naturalBounds;
 743     }
 744 
 745     /**
 746      * Creates a copy of this <code>TextLayout</code>.
 747      */
 748     protected Object clone() {
 749         /*
 750          * !!! I think this is safe.  Once created, nothing mutates the
 751          * glyphvectors or arrays.  But we need to make sure.
 752          * {jbr} actually, that's not quite true.  The justification code
 753          * mutates after cloning.  It doesn't actually change the glyphvectors
 754          * (that's impossible) but it replaces them with justified sets.  This
 755          * is a problem for GlyphIterator creation, since new GlyphIterators
 756          * are created by cloning a prototype.  If the prototype has outdated
 757          * glyphvectors, so will the new ones.  A partial solution is to set the
 758          * prototypical GlyphIterator to null when the glyphvectors change.  If
 759          * you forget this one time, you're hosed.
 760          */
 761         try {
 762             return super.clone();
 763         }
 764         catch (CloneNotSupportedException e) {
 765             throw new InternalError(e);
 766         }
 767     }
 768 
 769     /*
 770      * Utility to throw an expection if an invalid TextHitInfo is passed
 771      * as a parameter.  Avoids code duplication.
 772      */
 773     private void checkTextHit(TextHitInfo hit) {
 774         if (hit == null) {
 775             throw new IllegalArgumentException("TextHitInfo is null.");
 776         }
 777 
 778         if (hit.getInsertionIndex() < 0 ||
 779             hit.getInsertionIndex() > characterCount) {
 780             throw new IllegalArgumentException("TextHitInfo is out of range");
 781         }
 782     }
 783 
 784     /**
 785      * Creates a copy of this <code>TextLayout</code> justified to the
 786      * specified width.
 787      * <p>
 788      * If this <code>TextLayout</code> has already been justified, an
 789      * exception is thrown.  If this <code>TextLayout</code> object's
 790      * justification ratio is zero, a <code>TextLayout</code> identical
 791      * to this <code>TextLayout</code> is returned.
 792      * @param justificationWidth the width to use when justifying the line.
 793      * For best results, it should not be too different from the current
 794      * advance of the line.
 795      * @return a <code>TextLayout</code> justified to the specified width.
 796      * @exception Error if this layout has already been justified, an Error is
 797      * thrown.
 798      */
 799     public TextLayout getJustifiedLayout(float justificationWidth) {
 800 
 801         if (justificationWidth <= 0) {
 802             throw new IllegalArgumentException("justificationWidth <= 0 passed to TextLayout.getJustifiedLayout()");
 803         }
 804 
 805         if (justifyRatio == ALREADY_JUSTIFIED) {
 806             throw new Error("Can't justify again.");
 807         }
 808 
 809         ensureCache(); // make sure textLine is not null
 810 
 811         // default justification range to exclude trailing logical whitespace
 812         int limit = characterCount;
 813         while (limit > 0 && textLine.isCharWhitespace(limit-1)) {
 814             --limit;
 815         }
 816 
 817         TextLine newLine = textLine.getJustifiedLine(justificationWidth, justifyRatio, 0, limit);
 818         if (newLine != null) {
 819             return new TextLayout(newLine, baseline, baselineOffsets, ALREADY_JUSTIFIED);
 820         }
 821 
 822         return this;
 823     }
 824 
 825     /**
 826      * Justify this layout.  Overridden by subclassers to control justification
 827      * (if there were subclassers, that is...)
 828      *
 829      * The layout will only justify if the paragraph attributes (from the
 830      * source text, possibly defaulted by the layout attributes) indicate a
 831      * non-zero justification ratio.  The text will be justified to the
 832      * indicated width.  The current implementation also adjusts hanging
 833      * punctuation and trailing whitespace to overhang the justification width.
 834      * Once justified, the layout may not be rejustified.
 835      * <p>
 836      * Some code may rely on immutablity of layouts.  Subclassers should not
 837      * call this directly, but instead should call getJustifiedLayout, which
 838      * will call this method on a clone of this layout, preserving
 839      * the original.
 840      *
 841      * @param justificationWidth the width to use when justifying the line.
 842      * For best results, it should not be too different from the current
 843      * advance of the line.
 844      * @see #getJustifiedLayout(float)
 845      */
 846     protected void handleJustify(float justificationWidth) {
 847       // never called
 848     }
 849 
 850 
 851     /**
 852      * Returns the baseline for this <code>TextLayout</code>.
 853      * The baseline is one of the values defined in <code>Font</code>,
 854      * which are roman, centered and hanging.  Ascent and descent are
 855      * relative to this baseline.  The <code>baselineOffsets</code>
 856      * are also relative to this baseline.
 857      * @return the baseline of this <code>TextLayout</code>.
 858      * @see #getBaselineOffsets()
 859      * @see Font
 860      */
 861     public byte getBaseline() {
 862         return baseline;
 863     }
 864 
 865     /**
 866      * Returns the offsets array for the baselines used for this
 867      * <code>TextLayout</code>.
 868      * <p>
 869      * The array is indexed by one of the values defined in
 870      * <code>Font</code>, which are roman, centered and hanging.  The
 871      * values are relative to this <code>TextLayout</code> object's
 872      * baseline, so that <code>getBaselineOffsets[getBaseline()] == 0</code>.
 873      * Offsets are added to the position of the <code>TextLayout</code>
 874      * object's baseline to get the position for the new baseline.
 875      * @return the offsets array containing the baselines used for this
 876      *    <code>TextLayout</code>.
 877      * @see #getBaseline()
 878      * @see Font
 879      */
 880     public float[] getBaselineOffsets() {
 881         float[] offsets = new float[baselineOffsets.length];
 882         System.arraycopy(baselineOffsets, 0, offsets, 0, offsets.length);
 883         return offsets;
 884     }
 885 
 886     /**
 887      * Returns the advance of this <code>TextLayout</code>.
 888      * The advance is the distance from the origin to the advance of the
 889      * rightmost (bottommost) character.  This is in baseline-relative
 890      * coordinates.
 891      * @return the advance of this <code>TextLayout</code>.
 892      */
 893     public float getAdvance() {
 894         ensureCache();
 895         return lineMetrics.advance;
 896     }
 897 
 898     /**
 899      * Returns the advance of this <code>TextLayout</code>, minus trailing
 900      * whitespace.  This is in baseline-relative coordinates.
 901      * @return the advance of this <code>TextLayout</code> without the
 902      *      trailing whitespace.
 903      * @see #getAdvance()
 904      */
 905     public float getVisibleAdvance() {
 906         ensureCache();
 907         return visibleAdvance;
 908     }
 909 
 910     /**
 911      * Returns the ascent of this <code>TextLayout</code>.
 912      * The ascent is the distance from the top (right) of the
 913      * <code>TextLayout</code> to the baseline.  It is always either
 914      * positive or zero.  The ascent is sufficient to
 915      * accommodate superscripted text and is the maximum of the sum of the
 916      * ascent, offset, and baseline of each glyph.  The ascent is
 917      * the maximum ascent from the baseline of all the text in the
 918      * TextLayout.  It is in baseline-relative coordinates.
 919      * @return the ascent of this <code>TextLayout</code>.
 920      */
 921     public float getAscent() {
 922         ensureCache();
 923         return lineMetrics.ascent;
 924     }
 925 
 926     /**
 927      * Returns the descent of this <code>TextLayout</code>.
 928      * The descent is the distance from the baseline to the bottom (left) of
 929      * the <code>TextLayout</code>.  It is always either positive or zero.
 930      * The descent is sufficient to accommodate subscripted text and is the
 931      * maximum of the sum of the descent, offset, and baseline of each glyph.
 932      * This is the maximum descent from the baseline of all the text in
 933      * the TextLayout.  It is in baseline-relative coordinates.
 934      * @return the descent of this <code>TextLayout</code>.
 935      */
 936     public float getDescent() {
 937         ensureCache();
 938         return lineMetrics.descent;
 939     }
 940 
 941     /**
 942      * Returns the leading of the <code>TextLayout</code>.
 943      * The leading is the suggested interline spacing for this
 944      * <code>TextLayout</code>.  This is in baseline-relative
 945      * coordinates.
 946      * <p>
 947      * The leading is computed from the leading, descent, and baseline
 948      * of all glyphvectors in the <code>TextLayout</code>.  The algorithm
 949      * is roughly as follows:
 950      * <blockquote><pre>
 951      * maxD = 0;
 952      * maxDL = 0;
 953      * for (GlyphVector g in all glyphvectors) {
 954      *    maxD = max(maxD, g.getDescent() + offsets[g.getBaseline()]);
 955      *    maxDL = max(maxDL, g.getDescent() + g.getLeading() +
 956      *                       offsets[g.getBaseline()]);
 957      * }
 958      * return maxDL - maxD;
 959      * </pre></blockquote>
 960      * @return the leading of this <code>TextLayout</code>.
 961      */
 962     public float getLeading() {
 963         ensureCache();
 964         return lineMetrics.leading;
 965     }
 966 
 967     /**
 968      * Returns the bounds of this <code>TextLayout</code>.
 969      * The bounds are in standard coordinates.
 970      * <p>Due to rasterization effects, this bounds might not enclose all of the
 971      * pixels rendered by the TextLayout.</p>
 972      * It might not coincide exactly with the ascent, descent,
 973      * origin or advance of the <code>TextLayout</code>.
 974      * @return a {@link Rectangle2D} that is the bounds of this
 975      *        <code>TextLayout</code>.
 976      */
 977     public Rectangle2D getBounds() {
 978         ensureCache();
 979 
 980         if (boundsRect == null) {
 981             Rectangle2D vb = textLine.getVisualBounds();
 982             if (dx != 0 || dy != 0) {
 983                 vb.setRect(vb.getX() - dx,
 984                            vb.getY() - dy,
 985                            vb.getWidth(),
 986                            vb.getHeight());
 987             }
 988             boundsRect = vb;
 989         }
 990 
 991         Rectangle2D bounds = new Rectangle2D.Float();
 992         bounds.setRect(boundsRect);
 993 
 994         return bounds;
 995     }
 996 
 997     /**
 998      * Returns the pixel bounds of this <code>TextLayout</code> when
 999      * rendered in a graphics with the given
1000      * <code>FontRenderContext</code> at the given location.  The
1001      * graphics render context need not be the same as the
1002      * <code>FontRenderContext</code> used to create this
1003      * <code>TextLayout</code>, and can be null.  If it is null, the
1004      * <code>FontRenderContext</code> of this <code>TextLayout</code>
1005      * is used.
1006      * @param frc the <code>FontRenderContext</code> of the <code>Graphics</code>.
1007      * @param x the x-coordinate at which to render this <code>TextLayout</code>.
1008      * @param y the y-coordinate at which to render this <code>TextLayout</code>.
1009      * @return a <code>Rectangle</code> bounding the pixels that would be affected.
1010      * @see GlyphVector#getPixelBounds
1011      * @since 1.6
1012      */
1013     public Rectangle getPixelBounds(FontRenderContext frc, float x, float y) {
1014         return textLine.getPixelBounds(frc, x, y);
1015     }
1016 
1017     /**
1018      * Returns <code>true</code> if this <code>TextLayout</code> has
1019      * a left-to-right base direction or <code>false</code> if it has
1020      * a right-to-left base direction.  The <code>TextLayout</code>
1021      * has a base direction of either left-to-right (LTR) or
1022      * right-to-left (RTL).  The base direction is independent of the
1023      * actual direction of text on the line, which may be either LTR,
1024      * RTL, or mixed. Left-to-right layouts by default should position
1025      * flush left.  If the layout is on a tabbed line, the
1026      * tabs run left to right, so that logically successive layouts position
1027      * left to right.  The opposite is true for RTL layouts. By default they
1028      * should position flush left, and tabs run right-to-left.
1029      * @return <code>true</code> if the base direction of this
1030      *         <code>TextLayout</code> is left-to-right; <code>false</code>
1031      *         otherwise.
1032      */
1033     public boolean isLeftToRight() {
1034         return textLine.isDirectionLTR();
1035     }
1036 
1037     /**
1038      * Returns <code>true</code> if this <code>TextLayout</code> is vertical.
1039      * @return <code>true</code> if this <code>TextLayout</code> is vertical;
1040      *      <code>false</code> otherwise.
1041      */
1042     public boolean isVertical() {
1043         return isVerticalLine;
1044     }
1045 
1046     /**
1047      * Returns the number of characters represented by this
1048      * <code>TextLayout</code>.
1049      * @return the number of characters in this <code>TextLayout</code>.
1050      */
1051     public int getCharacterCount() {
1052         return characterCount;
1053     }
1054 
1055     /*
1056      * carets and hit testing
1057      *
1058      * Positions on a text line are represented by instances of TextHitInfo.
1059      * Any TextHitInfo with characterOffset between 0 and characterCount-1,
1060      * inclusive, represents a valid position on the line.  Additionally,
1061      * [-1, trailing] and [characterCount, leading] are valid positions, and
1062      * represent positions at the logical start and end of the line,
1063      * respectively.
1064      *
1065      * The characterOffsets in TextHitInfo's used and returned by TextLayout
1066      * are relative to the beginning of the text layout, not necessarily to
1067      * the beginning of the text storage the client is using.
1068      *
1069      *
1070      * Every valid TextHitInfo has either one or two carets associated with it.
1071      * A caret is a visual location in the TextLayout indicating where text at
1072      * the TextHitInfo will be displayed on screen.  If a TextHitInfo
1073      * represents a location on a directional boundary, then there are two
1074      * possible visible positions for newly inserted text.  Consider the
1075      * following example, in which capital letters indicate right-to-left text,
1076      * and the overall line direction is left-to-right:
1077      *
1078      * Text Storage: [ a, b, C, D, E, f ]
1079      * Display:        a b E D C f
1080      *
1081      * The text hit info (1, t) represents the trailing side of 'b'.  If 'q',
1082      * a left-to-right character is inserted into the text storage at this
1083      * location, it will be displayed between the 'b' and the 'E':
1084      *
1085      * Text Storage: [ a, b, q, C, D, E, f ]
1086      * Display:        a b q E D C f
1087      *
1088      * However, if a 'W', which is right-to-left, is inserted into the storage
1089      * after 'b', the storage and display will be:
1090      *
1091      * Text Storage: [ a, b, W, C, D, E, f ]
1092      * Display:        a b E D C W f
1093      *
1094      * So, for the original text storage, two carets should be displayed for
1095      * location (1, t): one visually between 'b' and 'E' and one visually
1096      * between 'C' and 'f'.
1097      *
1098      *
1099      * When two carets are displayed for a TextHitInfo, one caret is the
1100      * 'strong' caret and the other is the 'weak' caret.  The strong caret
1101      * indicates where an inserted character will be displayed when that
1102      * character's direction is the same as the direction of the TextLayout.
1103      * The weak caret shows where an character inserted character will be
1104      * displayed when the character's direction is opposite that of the
1105      * TextLayout.
1106      *
1107      *
1108      * Clients should not be overly concerned with the details of correct
1109      * caret display. TextLayout.getCaretShapes(TextHitInfo) will return an
1110      * array of two paths representing where carets should be displayed.
1111      * The first path in the array is the strong caret; the second element,
1112      * if non-null, is the weak caret.  If the second element is null,
1113      * then there is no weak caret for the given TextHitInfo.
1114      *
1115      *
1116      * Since text can be visually reordered, logically consecutive
1117      * TextHitInfo's may not be visually consecutive.  One implication of this
1118      * is that a client cannot tell from inspecting a TextHitInfo whether the
1119      * hit represents the first (or last) caret in the layout.  Clients
1120      * can call getVisualOtherHit();  if the visual companion is
1121      * (-1, TRAILING) or (characterCount, LEADING), then the hit is at the
1122      * first (last) caret position in the layout.
1123      */
1124 
1125     private float[] getCaretInfo(int caret,
1126                                  Rectangle2D bounds,
1127                                  float[] info) {
1128 
1129         float top1X, top2X;
1130         float bottom1X, bottom2X;
1131 
1132         if (caret == 0 || caret == characterCount) {
1133 
1134             float pos;
1135             int logIndex;
1136             if (caret == characterCount) {
1137                 logIndex = textLine.visualToLogical(characterCount-1);
1138                 pos = textLine.getCharLinePosition(logIndex)
1139                                         + textLine.getCharAdvance(logIndex);
1140             }
1141             else {
1142                 logIndex = textLine.visualToLogical(caret);
1143                 pos = textLine.getCharLinePosition(logIndex);
1144             }
1145             float angle = textLine.getCharAngle(logIndex);
1146             float shift = textLine.getCharShift(logIndex);
1147             pos += angle * shift;
1148             top1X = top2X = pos + angle*textLine.getCharAscent(logIndex);
1149             bottom1X = bottom2X = pos - angle*textLine.getCharDescent(logIndex);
1150         }
1151         else {
1152 
1153             {
1154                 int logIndex = textLine.visualToLogical(caret-1);
1155                 float angle1 = textLine.getCharAngle(logIndex);
1156                 float pos1 = textLine.getCharLinePosition(logIndex)
1157                                     + textLine.getCharAdvance(logIndex);
1158                 if (angle1 != 0) {
1159                     pos1 += angle1 * textLine.getCharShift(logIndex);
1160                     top1X = pos1 + angle1*textLine.getCharAscent(logIndex);
1161                     bottom1X = pos1 - angle1*textLine.getCharDescent(logIndex);
1162                 }
1163                 else {
1164                     top1X = bottom1X = pos1;
1165                 }
1166             }
1167             {
1168                 int logIndex = textLine.visualToLogical(caret);
1169                 float angle2 = textLine.getCharAngle(logIndex);
1170                 float pos2 = textLine.getCharLinePosition(logIndex);
1171                 if (angle2 != 0) {
1172                     pos2 += angle2*textLine.getCharShift(logIndex);
1173                     top2X = pos2 + angle2*textLine.getCharAscent(logIndex);
1174                     bottom2X = pos2 - angle2*textLine.getCharDescent(logIndex);
1175                 }
1176                 else {
1177                     top2X = bottom2X = pos2;
1178                 }
1179             }
1180         }
1181 
1182         float topX = (top1X + top2X) / 2;
1183         float bottomX = (bottom1X + bottom2X) / 2;
1184 
1185         if (info == null) {
1186             info = new float[2];
1187         }
1188 
1189         if (isVerticalLine) {
1190             info[1] = (float) ((topX - bottomX) / bounds.getWidth());
1191             info[0] = (float) (topX + (info[1]*bounds.getX()));
1192         }
1193         else {
1194             info[1] = (float) ((topX - bottomX) / bounds.getHeight());
1195             info[0] = (float) (bottomX + (info[1]*bounds.getMaxY()));
1196         }
1197 
1198         return info;
1199     }
1200 
1201     /**
1202      * Returns information about the caret corresponding to <code>hit</code>.
1203      * The first element of the array is the intersection of the caret with
1204      * the baseline, as a distance along the baseline. The second element
1205      * of the array is the inverse slope (run/rise) of the caret, measured
1206      * with respect to the baseline at that point.
1207      * <p>
1208      * This method is meant for informational use.  To display carets, it
1209      * is better to use <code>getCaretShapes</code>.
1210      * @param hit a hit on a character in this <code>TextLayout</code>
1211      * @param bounds the bounds to which the caret info is constructed.
1212      *     The bounds is in baseline-relative coordinates.
1213      * @return a two-element array containing the position and slope of
1214      * the caret.  The returned caret info is in baseline-relative coordinates.
1215      * @see #getCaretShapes(int, Rectangle2D, TextLayout.CaretPolicy)
1216      * @see Font#getItalicAngle
1217      */
1218     public float[] getCaretInfo(TextHitInfo hit, Rectangle2D bounds) {
1219         ensureCache();
1220         checkTextHit(hit);
1221 
1222         return getCaretInfoTestInternal(hit, bounds);
1223     }
1224 
1225     // this version provides extra info in the float array
1226     // the first two values are as above
1227     // the next four values are the endpoints of the caret, as computed
1228     // using the hit character's offset (baseline + ssoffset) and
1229     // natural ascent and descent.
1230     // these  values are trimmed to the bounds where required to fit,
1231     // but otherwise independent of it.
1232     private float[] getCaretInfoTestInternal(TextHitInfo hit, Rectangle2D bounds) {
1233         ensureCache();
1234         checkTextHit(hit);
1235 
1236         float[] info = new float[6];
1237 
1238         // get old data first
1239         getCaretInfo(hitToCaret(hit), bounds, info);
1240 
1241         // then add our new data
1242         double iangle, ixbase, p1x, p1y, p2x, p2y;
1243 
1244         int charix = hit.getCharIndex();
1245         boolean lead = hit.isLeadingEdge();
1246         boolean ltr = textLine.isDirectionLTR();
1247         boolean horiz = !isVertical();
1248 
1249         if (charix == -1 || charix == characterCount) {
1250             // !!! note: want non-shifted, baseline ascent and descent here!
1251             // TextLine should return appropriate line metrics object for these values
1252             TextLineMetrics m = textLine.getMetrics();
1253             boolean low = ltr == (charix == -1);
1254             iangle = 0;
1255             if (horiz) {
1256                 p1x = p2x = low ? 0 : m.advance;
1257                 p1y = -m.ascent;
1258                 p2y = m.descent;
1259             } else {
1260                 p1y = p2y = low ? 0 : m.advance;
1261                 p1x = m.descent;
1262                 p2x = m.ascent;
1263             }
1264         } else {
1265             CoreMetrics thiscm = textLine.getCoreMetricsAt(charix);
1266             iangle = thiscm.italicAngle;
1267             ixbase = textLine.getCharLinePosition(charix, lead);
1268             if (thiscm.baselineIndex < 0) {
1269                 // this is a graphic, no italics, use entire line height for caret
1270                 TextLineMetrics m = textLine.getMetrics();
1271                 if (horiz) {
1272                     p1x = p2x = ixbase;
1273                     if (thiscm.baselineIndex == GraphicAttribute.TOP_ALIGNMENT) {
1274                         p1y = -m.ascent;
1275                         p2y = p1y + thiscm.height;
1276                     } else {
1277                         p2y = m.descent;
1278                         p1y = p2y - thiscm.height;
1279                     }
1280                 } else {
1281                     p1y = p2y = ixbase;
1282                     p1x = m.descent;
1283                     p2x = m.ascent;
1284                     // !!! top/bottom adjustment not implemented for vertical
1285                 }
1286             } else {
1287                 float bo = baselineOffsets[thiscm.baselineIndex];
1288                 if (horiz) {
1289                     ixbase += iangle * thiscm.ssOffset;
1290                     p1x = ixbase + iangle * thiscm.ascent;
1291                     p2x = ixbase - iangle * thiscm.descent;
1292                     p1y = bo - thiscm.ascent;
1293                     p2y = bo + thiscm.descent;
1294                 } else {
1295                     ixbase -= iangle * thiscm.ssOffset;
1296                     p1y = ixbase + iangle * thiscm.ascent;
1297                     p2y = ixbase - iangle * thiscm.descent;
1298                     p1x = bo + thiscm.ascent;
1299                     p2x = bo + thiscm.descent;
1300                 }
1301             }
1302         }
1303 
1304         info[2] = (float)p1x;
1305         info[3] = (float)p1y;
1306         info[4] = (float)p2x;
1307         info[5] = (float)p2y;
1308 
1309         return info;
1310     }
1311 
1312     /**
1313      * Returns information about the caret corresponding to <code>hit</code>.
1314      * This method is a convenience overload of <code>getCaretInfo</code> and
1315      * uses the natural bounds of this <code>TextLayout</code>.
1316      * @param hit a hit on a character in this <code>TextLayout</code>
1317      * @return the information about a caret corresponding to a hit.  The
1318      *     returned caret info is in baseline-relative coordinates.
1319      */
1320     public float[] getCaretInfo(TextHitInfo hit) {
1321 
1322         return getCaretInfo(hit, getNaturalBounds());
1323     }
1324 
1325     /**
1326      * Returns a caret index corresponding to <code>hit</code>.
1327      * Carets are numbered from left to right (top to bottom) starting from
1328      * zero. This always places carets next to the character hit, on the
1329      * indicated side of the character.
1330      * @param hit a hit on a character in this <code>TextLayout</code>
1331      * @return a caret index corresponding to the specified hit.
1332      */
1333     private int hitToCaret(TextHitInfo hit) {
1334 
1335         int hitIndex = hit.getCharIndex();
1336 
1337         if (hitIndex < 0) {
1338             return textLine.isDirectionLTR() ? 0 : characterCount;
1339         } else if (hitIndex >= characterCount) {
1340             return textLine.isDirectionLTR() ? characterCount : 0;
1341         }
1342 
1343         int visIndex = textLine.logicalToVisual(hitIndex);
1344 
1345         if (hit.isLeadingEdge() != textLine.isCharLTR(hitIndex)) {
1346             ++visIndex;
1347         }
1348 
1349         return visIndex;
1350     }
1351 
1352     /**
1353      * Given a caret index, return a hit whose caret is at the index.
1354      * The hit is NOT guaranteed to be strong!!!
1355      *
1356      * @param caret a caret index.
1357      * @return a hit on this layout whose strong caret is at the requested
1358      * index.
1359      */
1360     private TextHitInfo caretToHit(int caret) {
1361 
1362         if (caret == 0 || caret == characterCount) {
1363 
1364             if ((caret == characterCount) == textLine.isDirectionLTR()) {
1365                 return TextHitInfo.leading(characterCount);
1366             }
1367             else {
1368                 return TextHitInfo.trailing(-1);
1369             }
1370         }
1371         else {
1372 
1373             int charIndex = textLine.visualToLogical(caret);
1374             boolean leading = textLine.isCharLTR(charIndex);
1375 
1376             return leading? TextHitInfo.leading(charIndex)
1377                             : TextHitInfo.trailing(charIndex);
1378         }
1379     }
1380 
1381     private boolean caretIsValid(int caret) {
1382 
1383         if (caret == characterCount || caret == 0) {
1384             return true;
1385         }
1386 
1387         int offset = textLine.visualToLogical(caret);
1388 
1389         if (!textLine.isCharLTR(offset)) {
1390             offset = textLine.visualToLogical(caret-1);
1391             if (textLine.isCharLTR(offset)) {
1392                 return true;
1393             }
1394         }
1395 
1396         // At this point, the leading edge of the character
1397         // at offset is at the given caret.
1398 
1399         return textLine.caretAtOffsetIsValid(offset);
1400     }
1401 
1402     /**
1403      * Returns the hit for the next caret to the right (bottom); if there
1404      * is no such hit, returns <code>null</code>.
1405      * If the hit character index is out of bounds, an
1406      * {@link IllegalArgumentException} is thrown.
1407      * @param hit a hit on a character in this layout
1408      * @return a hit whose caret appears at the next position to the
1409      * right (bottom) of the caret of the provided hit or <code>null</code>.
1410      */
1411     public TextHitInfo getNextRightHit(TextHitInfo hit) {
1412         ensureCache();
1413         checkTextHit(hit);
1414 
1415         int caret = hitToCaret(hit);
1416 
1417         if (caret == characterCount) {
1418             return null;
1419         }
1420 
1421         do {
1422             ++caret;
1423         } while (!caretIsValid(caret));
1424 
1425         return caretToHit(caret);
1426     }
1427 
1428     /**
1429      * Returns the hit for the next caret to the right (bottom); if no
1430      * such hit, returns <code>null</code>.  The hit is to the right of
1431      * the strong caret at the specified offset, as determined by the
1432      * specified policy.
1433      * The returned hit is the stronger of the two possible
1434      * hits, as determined by the specified policy.
1435      * @param offset an insertion offset in this <code>TextLayout</code>.
1436      * Cannot be less than 0 or greater than this <code>TextLayout</code>
1437      * object's character count.
1438      * @param policy the policy used to select the strong caret
1439      * @return a hit whose caret appears at the next position to the
1440      * right (bottom) of the caret of the provided hit, or <code>null</code>.
1441      */
1442     public TextHitInfo getNextRightHit(int offset, CaretPolicy policy) {
1443 
1444         if (offset < 0 || offset > characterCount) {
1445             throw new IllegalArgumentException("Offset out of bounds in TextLayout.getNextRightHit()");
1446         }
1447 
1448         if (policy == null) {
1449             throw new IllegalArgumentException("Null CaretPolicy passed to TextLayout.getNextRightHit()");
1450         }
1451 
1452         TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
1453         TextHitInfo hit2 = hit1.getOtherHit();
1454 
1455         TextHitInfo nextHit = getNextRightHit(policy.getStrongCaret(hit1, hit2, this));
1456 
1457         if (nextHit != null) {
1458             TextHitInfo otherHit = getVisualOtherHit(nextHit);
1459             return policy.getStrongCaret(otherHit, nextHit, this);
1460         }
1461         else {
1462             return null;
1463         }
1464     }
1465 
1466     /**
1467      * Returns the hit for the next caret to the right (bottom); if no
1468      * such hit, returns <code>null</code>.  The hit is to the right of
1469      * the strong caret at the specified offset, as determined by the
1470      * default policy.
1471      * The returned hit is the stronger of the two possible
1472      * hits, as determined by the default policy.
1473      * @param offset an insertion offset in this <code>TextLayout</code>.
1474      * Cannot be less than 0 or greater than the <code>TextLayout</code>
1475      * object's character count.
1476      * @return a hit whose caret appears at the next position to the
1477      * right (bottom) of the caret of the provided hit, or <code>null</code>.
1478      */
1479     public TextHitInfo getNextRightHit(int offset) {
1480 
1481         return getNextRightHit(offset, DEFAULT_CARET_POLICY);
1482     }
1483 
1484     /**
1485      * Returns the hit for the next caret to the left (top); if no such
1486      * hit, returns <code>null</code>.
1487      * If the hit character index is out of bounds, an
1488      * <code>IllegalArgumentException</code> is thrown.
1489      * @param hit a hit on a character in this <code>TextLayout</code>.
1490      * @return a hit whose caret appears at the next position to the
1491      * left (top) of the caret of the provided hit, or <code>null</code>.
1492      */
1493     public TextHitInfo getNextLeftHit(TextHitInfo hit) {
1494         ensureCache();
1495         checkTextHit(hit);
1496 
1497         int caret = hitToCaret(hit);
1498 
1499         if (caret == 0) {
1500             return null;
1501         }
1502 
1503         do {
1504             --caret;
1505         } while(!caretIsValid(caret));
1506 
1507         return caretToHit(caret);
1508     }
1509 
1510     /**
1511      * Returns the hit for the next caret to the left (top); if no
1512      * such hit, returns <code>null</code>.  The hit is to the left of
1513      * the strong caret at the specified offset, as determined by the
1514      * specified policy.
1515      * The returned hit is the stronger of the two possible
1516      * hits, as determined by the specified policy.
1517      * @param offset an insertion offset in this <code>TextLayout</code>.
1518      * Cannot be less than 0 or greater than this <code>TextLayout</code>
1519      * object's character count.
1520      * @param policy the policy used to select the strong caret
1521      * @return a hit whose caret appears at the next position to the
1522      * left (top) of the caret of the provided hit, or <code>null</code>.
1523      */
1524     public TextHitInfo getNextLeftHit(int offset, CaretPolicy policy) {
1525 
1526         if (policy == null) {
1527             throw new IllegalArgumentException("Null CaretPolicy passed to TextLayout.getNextLeftHit()");
1528         }
1529 
1530         if (offset < 0 || offset > characterCount) {
1531             throw new IllegalArgumentException("Offset out of bounds in TextLayout.getNextLeftHit()");
1532         }
1533 
1534         TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
1535         TextHitInfo hit2 = hit1.getOtherHit();
1536 
1537         TextHitInfo nextHit = getNextLeftHit(policy.getStrongCaret(hit1, hit2, this));
1538 
1539         if (nextHit != null) {
1540             TextHitInfo otherHit = getVisualOtherHit(nextHit);
1541             return policy.getStrongCaret(otherHit, nextHit, this);
1542         }
1543         else {
1544             return null;
1545         }
1546     }
1547 
1548     /**
1549      * Returns the hit for the next caret to the left (top); if no
1550      * such hit, returns <code>null</code>.  The hit is to the left of
1551      * the strong caret at the specified offset, as determined by the
1552      * default policy.
1553      * The returned hit is the stronger of the two possible
1554      * hits, as determined by the default policy.
1555      * @param offset an insertion offset in this <code>TextLayout</code>.
1556      * Cannot be less than 0 or greater than this <code>TextLayout</code>
1557      * object's character count.
1558      * @return a hit whose caret appears at the next position to the
1559      * left (top) of the caret of the provided hit, or <code>null</code>.
1560      */
1561     public TextHitInfo getNextLeftHit(int offset) {
1562 
1563         return getNextLeftHit(offset, DEFAULT_CARET_POLICY);
1564     }
1565 
1566     /**
1567      * Returns the hit on the opposite side of the specified hit's caret.
1568      * @param hit the specified hit
1569      * @return a hit that is on the opposite side of the specified hit's
1570      *    caret.
1571      */
1572     public TextHitInfo getVisualOtherHit(TextHitInfo hit) {
1573 
1574         ensureCache();
1575         checkTextHit(hit);
1576 
1577         int hitCharIndex = hit.getCharIndex();
1578 
1579         int charIndex;
1580         boolean leading;
1581 
1582         if (hitCharIndex == -1 || hitCharIndex == characterCount) {
1583 
1584             int visIndex;
1585             if (textLine.isDirectionLTR() == (hitCharIndex == -1)) {
1586                 visIndex = 0;
1587             }
1588             else {
1589                 visIndex = characterCount-1;
1590             }
1591 
1592             charIndex = textLine.visualToLogical(visIndex);
1593 
1594             if (textLine.isDirectionLTR() == (hitCharIndex == -1)) {
1595                 // at left end
1596                 leading = textLine.isCharLTR(charIndex);
1597             }
1598             else {
1599                 // at right end
1600                 leading = !textLine.isCharLTR(charIndex);
1601             }
1602         }
1603         else {
1604 
1605             int visIndex = textLine.logicalToVisual(hitCharIndex);
1606 
1607             boolean movedToRight;
1608             if (textLine.isCharLTR(hitCharIndex) == hit.isLeadingEdge()) {
1609                 --visIndex;
1610                 movedToRight = false;
1611             }
1612             else {
1613                 ++visIndex;
1614                 movedToRight = true;
1615             }
1616 
1617             if (visIndex > -1 && visIndex < characterCount) {
1618                 charIndex = textLine.visualToLogical(visIndex);
1619                 leading = movedToRight == textLine.isCharLTR(charIndex);
1620             }
1621             else {
1622                 charIndex =
1623                     (movedToRight == textLine.isDirectionLTR())? characterCount : -1;
1624                 leading = charIndex == characterCount;
1625             }
1626         }
1627 
1628         return leading? TextHitInfo.leading(charIndex) :
1629                                 TextHitInfo.trailing(charIndex);
1630     }
1631 
1632     private double[] getCaretPath(TextHitInfo hit, Rectangle2D bounds) {
1633         float[] info = getCaretInfo(hit, bounds);
1634         return new double[] { info[2], info[3], info[4], info[5] };
1635     }
1636 
1637     /**
1638      * Return an array of four floats corresponding the endpoints of the caret
1639      * x0, y0, x1, y1.
1640      *
1641      * This creates a line along the slope of the caret intersecting the
1642      * baseline at the caret
1643      * position, and extending from ascent above the baseline to descent below
1644      * it.
1645      */
1646     private double[] getCaretPath(int caret, Rectangle2D bounds,
1647                                   boolean clipToBounds) {
1648 
1649         float[] info = getCaretInfo(caret, bounds, null);
1650 
1651         double pos = info[0];
1652         double slope = info[1];
1653 
1654         double x0, y0, x1, y1;
1655         double x2 = -3141.59, y2 = -2.7; // values are there to make compiler happy
1656 
1657         double left = bounds.getX();
1658         double right = left + bounds.getWidth();
1659         double top = bounds.getY();
1660         double bottom = top + bounds.getHeight();
1661 
1662         boolean threePoints = false;
1663 
1664         if (isVerticalLine) {
1665 
1666             if (slope >= 0) {
1667                 x0 = left;
1668                 x1 = right;
1669             }
1670             else {
1671                 x1 = left;
1672                 x0 = right;
1673             }
1674 
1675             y0 = pos + x0 * slope;
1676             y1 = pos + x1 * slope;
1677 
1678             // y0 <= y1, always
1679 
1680             if (clipToBounds) {
1681                 if (y0 < top) {
1682                     if (slope <= 0 || y1 <= top) {
1683                         y0 = y1 = top;
1684                     }
1685                     else {
1686                         threePoints = true;
1687                         y0 = top;
1688                         y2 = top;
1689                         x2 = x1 + (top-y1)/slope;
1690                         if (y1 > bottom) {
1691                             y1 = bottom;
1692                         }
1693                     }
1694                 }
1695                 else if (y1 > bottom) {
1696                     if (slope >= 0 || y0 >= bottom) {
1697                         y0 = y1 = bottom;
1698                     }
1699                     else {
1700                         threePoints = true;
1701                         y1 = bottom;
1702                         y2 = bottom;
1703                         x2 = x0 + (bottom-x1)/slope;
1704                     }
1705                 }
1706             }
1707 
1708         }
1709         else {
1710 
1711             if (slope >= 0) {
1712                 y0 = bottom;
1713                 y1 = top;
1714             }
1715             else {
1716                 y1 = bottom;
1717                 y0 = top;
1718             }
1719 
1720             x0 = pos - y0 * slope;
1721             x1 = pos - y1 * slope;
1722 
1723             // x0 <= x1, always
1724 
1725             if (clipToBounds) {
1726                 if (x0 < left) {
1727                     if (slope <= 0 || x1 <= left) {
1728                         x0 = x1 = left;
1729                     }
1730                     else {
1731                         threePoints = true;
1732                         x0 = left;
1733                         x2 = left;
1734                         y2 = y1 - (left-x1)/slope;
1735                         if (x1 > right) {
1736                             x1 = right;
1737                         }
1738                     }
1739                 }
1740                 else if (x1 > right) {
1741                     if (slope >= 0 || x0 >= right) {
1742                         x0 = x1 = right;
1743                     }
1744                     else {
1745                         threePoints = true;
1746                         x1 = right;
1747                         x2 = right;
1748                         y2 = y0 - (right-x0)/slope;
1749                     }
1750                 }
1751             }
1752         }
1753 
1754         return threePoints?
1755                     new double[] { x0, y0, x2, y2, x1, y1 } :
1756                     new double[] { x0, y0, x1, y1 };
1757     }
1758 
1759 
1760     private static GeneralPath pathToShape(double[] path, boolean close, LayoutPathImpl lp) {
1761         GeneralPath result = new GeneralPath(GeneralPath.WIND_EVEN_ODD, path.length);
1762         result.moveTo((float)path[0], (float)path[1]);
1763         for (int i = 2; i < path.length; i += 2) {
1764             result.lineTo((float)path[i], (float)path[i+1]);
1765         }
1766         if (close) {
1767             result.closePath();
1768         }
1769 
1770         if (lp != null) {
1771             result = (GeneralPath)lp.mapShape(result);
1772         }
1773         return result;
1774     }
1775 
1776     /**
1777      * Returns a {@link Shape} representing the caret at the specified
1778      * hit inside the specified bounds.
1779      * @param hit the hit at which to generate the caret
1780      * @param bounds the bounds of the <code>TextLayout</code> to use
1781      *    in generating the caret.  The bounds is in baseline-relative
1782      *    coordinates.
1783      * @return a <code>Shape</code> representing the caret.  The returned
1784      *    shape is in standard coordinates.
1785      */
1786     public Shape getCaretShape(TextHitInfo hit, Rectangle2D bounds) {
1787         ensureCache();
1788         checkTextHit(hit);
1789 
1790         if (bounds == null) {
1791             throw new IllegalArgumentException("Null Rectangle2D passed to TextLayout.getCaret()");
1792         }
1793 
1794         return pathToShape(getCaretPath(hit, bounds), false, textLine.getLayoutPath());
1795     }
1796 
1797     /**
1798      * Returns a <code>Shape</code> representing the caret at the specified
1799      * hit inside the natural bounds of this <code>TextLayout</code>.
1800      * @param hit the hit at which to generate the caret
1801      * @return a <code>Shape</code> representing the caret.  The returned
1802      *     shape is in standard coordinates.
1803      */
1804     public Shape getCaretShape(TextHitInfo hit) {
1805 
1806         return getCaretShape(hit, getNaturalBounds());
1807     }
1808 
1809     /**
1810      * Return the "stronger" of the TextHitInfos.  The TextHitInfos
1811      * should be logical or visual counterparts.  They are not
1812      * checked for validity.
1813      */
1814     private final TextHitInfo getStrongHit(TextHitInfo hit1, TextHitInfo hit2) {
1815 
1816         // right now we're using the following rule for strong hits:
1817         // A hit on a character with a lower level
1818         // is stronger than one on a character with a higher level.
1819         // If this rule ties, the hit on the leading edge of a character wins.
1820         // If THIS rule ties, hit1 wins.  Both rules shouldn't tie, unless the
1821         // infos aren't counterparts of some sort.
1822 
1823         byte hit1Level = getCharacterLevel(hit1.getCharIndex());
1824         byte hit2Level = getCharacterLevel(hit2.getCharIndex());
1825 
1826         if (hit1Level == hit2Level) {
1827             if (hit2.isLeadingEdge() && !hit1.isLeadingEdge()) {
1828                 return hit2;
1829             }
1830             else {
1831                 return hit1;
1832             }
1833         }
1834         else {
1835             return (hit1Level < hit2Level)? hit1 : hit2;
1836         }
1837     }
1838 
1839     /**
1840      * Returns the level of the character at <code>index</code>.
1841      * Indices -1 and <code>characterCount</code> are assigned the base
1842      * level of this <code>TextLayout</code>.
1843      * @param index the index of the character from which to get the level
1844      * @return the level of the character at the specified index.
1845      */
1846     public byte getCharacterLevel(int index) {
1847 
1848         // hmm, allow indices at endpoints?  For now, yes.
1849         if (index < -1 || index > characterCount) {
1850             throw new IllegalArgumentException("Index is out of range in getCharacterLevel.");
1851         }
1852 
1853         ensureCache();
1854         if (index == -1 || index == characterCount) {
1855              return (byte) (textLine.isDirectionLTR()? 0 : 1);
1856         }
1857 
1858         return textLine.getCharLevel(index);
1859     }
1860 
1861     /**
1862      * Returns two paths corresponding to the strong and weak caret.
1863      * @param offset an offset in this <code>TextLayout</code>
1864      * @param bounds the bounds to which to extend the carets.  The
1865      * bounds is in baseline-relative coordinates.
1866      * @param policy the specified <code>CaretPolicy</code>
1867      * @return an array of two paths.  Element zero is the strong
1868      * caret.  If there are two carets, element one is the weak caret,
1869      * otherwise it is <code>null</code>. The returned shapes
1870      * are in standard coordinates.
1871      */
1872     public Shape[] getCaretShapes(int offset, Rectangle2D bounds, CaretPolicy policy) {
1873 
1874         ensureCache();
1875 
1876         if (offset < 0 || offset > characterCount) {
1877             throw new IllegalArgumentException("Offset out of bounds in TextLayout.getCaretShapes()");
1878         }
1879 
1880         if (bounds == null) {
1881             throw new IllegalArgumentException("Null Rectangle2D passed to TextLayout.getCaretShapes()");
1882         }
1883 
1884         if (policy == null) {
1885             throw new IllegalArgumentException("Null CaretPolicy passed to TextLayout.getCaretShapes()");
1886         }
1887 
1888         Shape[] result = new Shape[2];
1889 
1890         TextHitInfo hit = TextHitInfo.afterOffset(offset);
1891 
1892         int hitCaret = hitToCaret(hit);
1893 
1894         LayoutPathImpl lp = textLine.getLayoutPath();
1895         Shape hitShape = pathToShape(getCaretPath(hit, bounds), false, lp);
1896         TextHitInfo otherHit = hit.getOtherHit();
1897         int otherCaret = hitToCaret(otherHit);
1898 
1899         if (hitCaret == otherCaret) {
1900             result[0] = hitShape;
1901         }
1902         else { // more than one caret
1903             Shape otherShape = pathToShape(getCaretPath(otherHit, bounds), false, lp);
1904 
1905             TextHitInfo strongHit = policy.getStrongCaret(hit, otherHit, this);
1906             boolean hitIsStrong = strongHit.equals(hit);
1907 
1908             if (hitIsStrong) {// then other is weak
1909                 result[0] = hitShape;
1910                 result[1] = otherShape;
1911             }
1912             else {
1913                 result[0] = otherShape;
1914                 result[1] = hitShape;
1915             }
1916         }
1917 
1918         return result;
1919     }
1920 
1921     /**
1922      * Returns two paths corresponding to the strong and weak caret.
1923      * This method is a convenience overload of <code>getCaretShapes</code>
1924      * that uses the default caret policy.
1925      * @param offset an offset in this <code>TextLayout</code>
1926      * @param bounds the bounds to which to extend the carets.  This is
1927      *     in baseline-relative coordinates.
1928      * @return two paths corresponding to the strong and weak caret as
1929      *    defined by the <code>DEFAULT_CARET_POLICY</code>.  These are
1930      *    in standard coordinates.
1931      */
1932     public Shape[] getCaretShapes(int offset, Rectangle2D bounds) {
1933         // {sfb} parameter checking is done in overloaded version
1934         return getCaretShapes(offset, bounds, DEFAULT_CARET_POLICY);
1935     }
1936 
1937     /**
1938      * Returns two paths corresponding to the strong and weak caret.
1939      * This method is a convenience overload of <code>getCaretShapes</code>
1940      * that uses the default caret policy and this <code>TextLayout</code>
1941      * object's natural bounds.
1942      * @param offset an offset in this <code>TextLayout</code>
1943      * @return two paths corresponding to the strong and weak caret as
1944      *    defined by the <code>DEFAULT_CARET_POLICY</code>.  These are
1945      *    in standard coordinates.
1946      */
1947     public Shape[] getCaretShapes(int offset) {
1948         // {sfb} parameter checking is done in overloaded version
1949         return getCaretShapes(offset, getNaturalBounds(), DEFAULT_CARET_POLICY);
1950     }
1951 
1952     // A utility to return a path enclosing the given path
1953     // Path0 must be left or top of path1
1954     // {jbr} no assumptions about size of path0, path1 anymore.
1955     private GeneralPath boundingShape(double[] path0, double[] path1) {
1956 
1957         // Really, we want the path to be a convex hull around all of the
1958         // points in path0 and path1.  But we can get by with less than
1959         // that.  We do need to prevent the two segments which
1960         // join path0 to path1 from crossing each other.  So, if we
1961         // traverse path0 from top to bottom, we'll traverse path1 from
1962         // bottom to top (and vice versa).
1963 
1964         GeneralPath result = pathToShape(path0, false, null);
1965 
1966         boolean sameDirection;
1967 
1968         if (isVerticalLine) {
1969             sameDirection = (path0[1] > path0[path0.length-1]) ==
1970                             (path1[1] > path1[path1.length-1]);
1971         }
1972         else {
1973             sameDirection = (path0[0] > path0[path0.length-2]) ==
1974                             (path1[0] > path1[path1.length-2]);
1975         }
1976 
1977         int start;
1978         int limit;
1979         int increment;
1980 
1981         if (sameDirection) {
1982             start = path1.length-2;
1983             limit = -2;
1984             increment = -2;
1985         }
1986         else {
1987             start = 0;
1988             limit = path1.length;
1989             increment = 2;
1990         }
1991 
1992         for (int i = start; i != limit; i += increment) {
1993             result.lineTo((float)path1[i], (float)path1[i+1]);
1994         }
1995 
1996         result.closePath();
1997 
1998         return result;
1999     }
2000 
2001     // A utility to convert a pair of carets into a bounding path
2002     // {jbr} Shape is never outside of bounds.
2003     private GeneralPath caretBoundingShape(int caret0,
2004                                            int caret1,
2005                                            Rectangle2D bounds) {
2006 
2007         if (caret0 > caret1) {
2008             int temp = caret0;
2009             caret0 = caret1;
2010             caret1 = temp;
2011         }
2012 
2013         return boundingShape(getCaretPath(caret0, bounds, true),
2014                              getCaretPath(caret1, bounds, true));
2015     }
2016 
2017     /*
2018      * A utility to return the path bounding the area to the left (top) of the
2019      * layout.
2020      * Shape is never outside of bounds.
2021      */
2022     private GeneralPath leftShape(Rectangle2D bounds) {
2023 
2024         double[] path0;
2025         if (isVerticalLine) {
2026             path0 = new double[] { bounds.getX(), bounds.getY(),
2027                                        bounds.getX() + bounds.getWidth(),
2028                                        bounds.getY() };
2029         } else {
2030             path0 = new double[] { bounds.getX(),
2031                                        bounds.getY() + bounds.getHeight(),
2032                                        bounds.getX(), bounds.getY() };
2033         }
2034 
2035         double[] path1 = getCaretPath(0, bounds, true);
2036 
2037         return boundingShape(path0, path1);
2038     }
2039 
2040     /*
2041      * A utility to return the path bounding the area to the right (bottom) of
2042      * the layout.
2043      */
2044     private GeneralPath rightShape(Rectangle2D bounds) {
2045         double[] path1;
2046         if (isVerticalLine) {
2047             path1 = new double[] {
2048                 bounds.getX(),
2049                 bounds.getY() + bounds.getHeight(),
2050                 bounds.getX() + bounds.getWidth(),
2051                 bounds.getY() + bounds.getHeight()
2052             };
2053         } else {
2054             path1 = new double[] {
2055                 bounds.getX() + bounds.getWidth(),
2056                 bounds.getY() + bounds.getHeight(),
2057                 bounds.getX() + bounds.getWidth(),
2058                 bounds.getY()
2059             };
2060         }
2061 
2062         double[] path0 = getCaretPath(characterCount, bounds, true);
2063 
2064         return boundingShape(path0, path1);
2065     }
2066 
2067     /**
2068      * Returns the logical ranges of text corresponding to a visual selection.
2069      * @param firstEndpoint an endpoint of the visual range
2070      * @param secondEndpoint the other endpoint of the visual range.
2071      * This endpoint can be less than <code>firstEndpoint</code>.
2072      * @return an array of integers representing start/limit pairs for the
2073      * selected ranges.
2074      * @see #getVisualHighlightShape(TextHitInfo, TextHitInfo, Rectangle2D)
2075      */
2076     public int[] getLogicalRangesForVisualSelection(TextHitInfo firstEndpoint,
2077                                                     TextHitInfo secondEndpoint) {
2078         ensureCache();
2079 
2080         checkTextHit(firstEndpoint);
2081         checkTextHit(secondEndpoint);
2082 
2083         // !!! probably want to optimize for all LTR text
2084 
2085         boolean[] included = new boolean[characterCount];
2086 
2087         int startIndex = hitToCaret(firstEndpoint);
2088         int limitIndex = hitToCaret(secondEndpoint);
2089 
2090         if (startIndex > limitIndex) {
2091             int t = startIndex;
2092             startIndex = limitIndex;
2093             limitIndex = t;
2094         }
2095 
2096         /*
2097          * now we have the visual indexes of the glyphs at the start and limit
2098          * of the selection range walk through runs marking characters that
2099          * were included in the visual range there is probably a more efficient
2100          * way to do this, but this ought to work, so hey
2101          */
2102 
2103         if (startIndex < limitIndex) {
2104             int visIndex = startIndex;
2105             while (visIndex < limitIndex) {
2106                 included[textLine.visualToLogical(visIndex)] = true;
2107                 ++visIndex;
2108             }
2109         }
2110 
2111         /*
2112          * count how many runs we have, ought to be one or two, but perhaps
2113          * things are especially weird
2114          */
2115         int count = 0;
2116         boolean inrun = false;
2117         for (int i = 0; i < characterCount; i++) {
2118             if (included[i] != inrun) {
2119                 inrun = !inrun;
2120                 if (inrun) {
2121                     count++;
2122                 }
2123             }
2124         }
2125 
2126         int[] ranges = new int[count * 2];
2127         count = 0;
2128         inrun = false;
2129         for (int i = 0; i < characterCount; i++) {
2130             if (included[i] != inrun) {
2131                 ranges[count++] = i;
2132                 inrun = !inrun;
2133             }
2134         }
2135         if (inrun) {
2136             ranges[count++] = characterCount;
2137         }
2138 
2139         return ranges;
2140     }
2141 
2142     /**
2143      * Returns a path enclosing the visual selection in the specified range,
2144      * extended to <code>bounds</code>.
2145      * <p>
2146      * If the selection includes the leftmost (topmost) position, the selection
2147      * is extended to the left (top) of <code>bounds</code>.  If the
2148      * selection includes the rightmost (bottommost) position, the selection
2149      * is extended to the right (bottom) of the bounds.  The height
2150      * (width on vertical lines) of the selection is always extended to
2151      * <code>bounds</code>.
2152      * <p>
2153      * Although the selection is always contiguous, the logically selected
2154      * text can be discontiguous on lines with mixed-direction text.  The
2155      * logical ranges of text selected can be retrieved using
2156      * <code>getLogicalRangesForVisualSelection</code>.  For example,
2157      * consider the text 'ABCdef' where capital letters indicate
2158      * right-to-left text, rendered on a right-to-left line, with a visual
2159      * selection from 0L (the leading edge of 'A') to 3T (the trailing edge
2160      * of 'd').  The text appears as follows, with bold underlined areas
2161      * representing the selection:
2162      * <br><pre>
2163      *    d<u><b>efCBA  </b></u>
2164      * </pre>
2165      * The logical selection ranges are 0-3, 4-6 (ABC, ef) because the
2166      * visually contiguous text is logically discontiguous.  Also note that
2167      * since the rightmost position on the layout (to the right of 'A') is
2168      * selected, the selection is extended to the right of the bounds.
2169      * @param firstEndpoint one end of the visual selection
2170      * @param secondEndpoint the other end of the visual selection
2171      * @param bounds the bounding rectangle to which to extend the selection.
2172      *     This is in baseline-relative coordinates.
2173      * @return a <code>Shape</code> enclosing the selection.  This is in
2174      *     standard coordinates.
2175      * @see #getLogicalRangesForVisualSelection(TextHitInfo, TextHitInfo)
2176      * @see #getLogicalHighlightShape(int, int, Rectangle2D)
2177      */
2178     public Shape getVisualHighlightShape(TextHitInfo firstEndpoint,
2179                                         TextHitInfo secondEndpoint,
2180                                         Rectangle2D bounds)
2181     {
2182         ensureCache();
2183 
2184         checkTextHit(firstEndpoint);
2185         checkTextHit(secondEndpoint);
2186 
2187         if(bounds == null) {
2188                 throw new IllegalArgumentException("Null Rectangle2D passed to TextLayout.getVisualHighlightShape()");
2189         }
2190 
2191         GeneralPath result = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
2192 
2193         int firstCaret = hitToCaret(firstEndpoint);
2194         int secondCaret = hitToCaret(secondEndpoint);
2195 
2196         result.append(caretBoundingShape(firstCaret, secondCaret, bounds),
2197                       false);
2198 
2199         if (firstCaret == 0 || secondCaret == 0) {
2200             GeneralPath ls = leftShape(bounds);
2201             if (!ls.getBounds().isEmpty())
2202                 result.append(ls, false);
2203         }
2204 
2205         if (firstCaret == characterCount || secondCaret == characterCount) {
2206             GeneralPath rs = rightShape(bounds);
2207             if (!rs.getBounds().isEmpty()) {
2208                 result.append(rs, false);
2209             }
2210         }
2211 
2212         LayoutPathImpl lp = textLine.getLayoutPath();
2213         if (lp != null) {
2214             result = (GeneralPath)lp.mapShape(result); // dlf cast safe?
2215         }
2216 
2217         return  result;
2218     }
2219 
2220     /**
2221      * Returns a <code>Shape</code> enclosing the visual selection in the
2222      * specified range, extended to the bounds.  This method is a
2223      * convenience overload of <code>getVisualHighlightShape</code> that
2224      * uses the natural bounds of this <code>TextLayout</code>.
2225      * @param firstEndpoint one end of the visual selection
2226      * @param secondEndpoint the other end of the visual selection
2227      * @return a <code>Shape</code> enclosing the selection.  This is
2228      *     in standard coordinates.
2229      */
2230     public Shape getVisualHighlightShape(TextHitInfo firstEndpoint,
2231                                              TextHitInfo secondEndpoint) {
2232         return getVisualHighlightShape(firstEndpoint, secondEndpoint, getNaturalBounds());
2233     }
2234 
2235     /**
2236      * Returns a <code>Shape</code> enclosing the logical selection in the
2237      * specified range, extended to the specified <code>bounds</code>.
2238      * <p>
2239      * If the selection range includes the first logical character, the
2240      * selection is extended to the portion of <code>bounds</code> before
2241      * the start of this <code>TextLayout</code>.  If the range includes
2242      * the last logical character, the selection is extended to the portion
2243      * of <code>bounds</code> after the end of this <code>TextLayout</code>.
2244      * The height (width on vertical lines) of the selection is always
2245      * extended to <code>bounds</code>.
2246      * <p>
2247      * The selection can be discontiguous on lines with mixed-direction text.
2248      * Only those characters in the logical range between start and limit
2249      * appear selected.  For example, consider the text 'ABCdef' where capital
2250      * letters indicate right-to-left text, rendered on a right-to-left line,
2251      * with a logical selection from 0 to 4 ('ABCd').  The text appears as
2252      * follows, with bold standing in for the selection, and underlining for
2253      * the extension:
2254      * <br><pre>
2255      *    <u><b>d</b></u>ef<u><b>CBA  </b></u>
2256      * </pre>
2257      * The selection is discontiguous because the selected characters are
2258      * visually discontiguous. Also note that since the range includes the
2259      * first logical character (A), the selection is extended to the portion
2260      * of the <code>bounds</code> before the start of the layout, which in
2261      * this case (a right-to-left line) is the right portion of the
2262      * <code>bounds</code>.
2263      * @param firstEndpoint an endpoint in the range of characters to select
2264      * @param secondEndpoint the other endpoint of the range of characters
2265      * to select. Can be less than <code>firstEndpoint</code>.  The range
2266      * includes the character at min(firstEndpoint, secondEndpoint), but
2267      * excludes max(firstEndpoint, secondEndpoint).
2268      * @param bounds the bounding rectangle to which to extend the selection.
2269      *     This is in baseline-relative coordinates.
2270      * @return an area enclosing the selection.  This is in standard
2271      *     coordinates.
2272      * @see #getVisualHighlightShape(TextHitInfo, TextHitInfo, Rectangle2D)
2273      */
2274     public Shape getLogicalHighlightShape(int firstEndpoint,
2275                                          int secondEndpoint,
2276                                          Rectangle2D bounds) {
2277         if (bounds == null) {
2278             throw new IllegalArgumentException("Null Rectangle2D passed to TextLayout.getLogicalHighlightShape()");
2279         }
2280 
2281         ensureCache();
2282 
2283         if (firstEndpoint > secondEndpoint) {
2284             int t = firstEndpoint;
2285             firstEndpoint = secondEndpoint;
2286             secondEndpoint = t;
2287         }
2288 
2289         if(firstEndpoint < 0 || secondEndpoint > characterCount) {
2290             throw new IllegalArgumentException("Range is invalid in TextLayout.getLogicalHighlightShape()");
2291         }
2292 
2293         GeneralPath result = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
2294 
2295         int[] carets = new int[10]; // would this ever not handle all cases?
2296         int count = 0;
2297 
2298         if (firstEndpoint < secondEndpoint) {
2299             int logIndex = firstEndpoint;
2300             do {
2301                 carets[count++] = hitToCaret(TextHitInfo.leading(logIndex));
2302                 boolean ltr = textLine.isCharLTR(logIndex);
2303 
2304                 do {
2305                     logIndex++;
2306                 } while (logIndex < secondEndpoint && textLine.isCharLTR(logIndex) == ltr);
2307 
2308                 int hitCh = logIndex;
2309                 carets[count++] = hitToCaret(TextHitInfo.trailing(hitCh - 1));
2310 
2311                 if (count == carets.length) {
2312                     int[] temp = new int[carets.length + 10];
2313                     System.arraycopy(carets, 0, temp, 0, count);
2314                     carets = temp;
2315                 }
2316             } while (logIndex < secondEndpoint);
2317         }
2318         else {
2319             count = 2;
2320             carets[0] = carets[1] = hitToCaret(TextHitInfo.leading(firstEndpoint));
2321         }
2322 
2323         // now create paths for pairs of carets
2324 
2325         for (int i = 0; i < count; i += 2) {
2326             result.append(caretBoundingShape(carets[i], carets[i+1], bounds),
2327                           false);
2328         }
2329 
2330         if (firstEndpoint != secondEndpoint) {
2331             if ((textLine.isDirectionLTR() && firstEndpoint == 0) || (!textLine.isDirectionLTR() &&
2332                                                                       secondEndpoint == characterCount)) {
2333                 GeneralPath ls = leftShape(bounds);
2334                 if (!ls.getBounds().isEmpty()) {
2335                     result.append(ls, false);
2336                 }
2337             }
2338 
2339             if ((textLine.isDirectionLTR() && secondEndpoint == characterCount) ||
2340                 (!textLine.isDirectionLTR() && firstEndpoint == 0)) {
2341 
2342                 GeneralPath rs = rightShape(bounds);
2343                 if (!rs.getBounds().isEmpty()) {
2344                     result.append(rs, false);
2345                 }
2346             }
2347         }
2348 
2349         LayoutPathImpl lp = textLine.getLayoutPath();
2350         if (lp != null) {
2351             result = (GeneralPath)lp.mapShape(result); // dlf cast safe?
2352         }
2353         return result;
2354     }
2355 
2356     /**
2357      * Returns a <code>Shape</code> enclosing the logical selection in the
2358      * specified range, extended to the natural bounds of this
2359      * <code>TextLayout</code>.  This method is a convenience overload of
2360      * <code>getLogicalHighlightShape</code> that uses the natural bounds of
2361      * this <code>TextLayout</code>.
2362      * @param firstEndpoint an endpoint in the range of characters to select
2363      * @param secondEndpoint the other endpoint of the range of characters
2364      * to select. Can be less than <code>firstEndpoint</code>.  The range
2365      * includes the character at min(firstEndpoint, secondEndpoint), but
2366      * excludes max(firstEndpoint, secondEndpoint).
2367      * @return a <code>Shape</code> enclosing the selection.  This is in
2368      *     standard coordinates.
2369      */
2370     public Shape getLogicalHighlightShape(int firstEndpoint, int secondEndpoint) {
2371 
2372         return getLogicalHighlightShape(firstEndpoint, secondEndpoint, getNaturalBounds());
2373     }
2374 
2375     /**
2376      * Returns the black box bounds of the characters in the specified range.
2377      * The black box bounds is an area consisting of the union of the bounding
2378      * boxes of all the glyphs corresponding to the characters between start
2379      * and limit.  This area can be disjoint.
2380      * @param firstEndpoint one end of the character range
2381      * @param secondEndpoint the other end of the character range.  Can be
2382      * less than <code>firstEndpoint</code>.
2383      * @return a <code>Shape</code> enclosing the black box bounds.  This is
2384      *     in standard coordinates.
2385      */
2386     public Shape getBlackBoxBounds(int firstEndpoint, int secondEndpoint) {
2387         ensureCache();
2388 
2389         if (firstEndpoint > secondEndpoint) {
2390             int t = firstEndpoint;
2391             firstEndpoint = secondEndpoint;
2392             secondEndpoint = t;
2393         }
2394 
2395         if (firstEndpoint < 0 || secondEndpoint > characterCount) {
2396             throw new IllegalArgumentException("Invalid range passed to TextLayout.getBlackBoxBounds()");
2397         }
2398 
2399         /*
2400          * return an area that consists of the bounding boxes of all the
2401          * characters from firstEndpoint to limit
2402          */
2403 
2404         GeneralPath result = new GeneralPath(GeneralPath.WIND_NON_ZERO);
2405 
2406         if (firstEndpoint < characterCount) {
2407             for (int logIndex = firstEndpoint;
2408                         logIndex < secondEndpoint;
2409                         logIndex++) {
2410 
2411                 Rectangle2D r = textLine.getCharBounds(logIndex);
2412                 if (!r.isEmpty()) {
2413                     result.append(r, false);
2414                 }
2415             }
2416         }
2417 
2418         if (dx != 0 || dy != 0) {
2419             AffineTransform tx = AffineTransform.getTranslateInstance(dx, dy);
2420             result = (GeneralPath)tx.createTransformedShape(result);
2421         }
2422         LayoutPathImpl lp = textLine.getLayoutPath();
2423         if (lp != null) {
2424             result = (GeneralPath)lp.mapShape(result);
2425         }
2426 
2427         //return new Highlight(result, false);
2428         return result;
2429     }
2430 
2431     /**
2432      * Returns the distance from the point (x,&nbsp;y) to the caret along
2433      * the line direction defined in <code>caretInfo</code>.  Distance is
2434      * negative if the point is to the left of the caret on a horizontal
2435      * line, or above the caret on a vertical line.
2436      * Utility for use by hitTestChar.
2437      */
2438     private float caretToPointDistance(float[] caretInfo, float x, float y) {
2439         // distanceOffBaseline is negative if you're 'above' baseline
2440 
2441         float lineDistance = isVerticalLine? y : x;
2442         float distanceOffBaseline = isVerticalLine? -x : y;
2443 
2444         return lineDistance - caretInfo[0] +
2445             (distanceOffBaseline*caretInfo[1]);
2446     }
2447 
2448     /**
2449      * Returns a <code>TextHitInfo</code> corresponding to the
2450      * specified point.
2451      * Coordinates outside the bounds of the <code>TextLayout</code>
2452      * map to hits on the leading edge of the first logical character,
2453      * or the trailing edge of the last logical character, as appropriate,
2454      * regardless of the position of that character in the line.  Only the
2455      * direction along the baseline is used to make this evaluation.
2456      * @param x the x offset from the origin of this
2457      *     <code>TextLayout</code>.  This is in standard coordinates.
2458      * @param y the y offset from the origin of this
2459      *     <code>TextLayout</code>.  This is in standard coordinates.
2460      * @param bounds the bounds of the <code>TextLayout</code>.  This
2461      *     is in baseline-relative coordinates.
2462      * @return a hit describing the character and edge (leading or trailing)
2463      *     under the specified point.
2464      */
2465     public TextHitInfo hitTestChar(float x, float y, Rectangle2D bounds) {
2466         // check boundary conditions
2467 
2468         LayoutPathImpl lp = textLine.getLayoutPath();
2469         boolean prev = false;
2470         if (lp != null) {
2471             Point2D.Float pt = new Point2D.Float(x, y);
2472             prev = lp.pointToPath(pt, pt);
2473             x = pt.x;
2474             y = pt.y;
2475         }
2476 
2477         if (isVertical()) {
2478             if (y < bounds.getMinY()) {
2479                 return TextHitInfo.leading(0);
2480             } else if (y >= bounds.getMaxY()) {
2481                 return TextHitInfo.trailing(characterCount-1);
2482             }
2483         } else {
2484             if (x < bounds.getMinX()) {
2485                 return isLeftToRight() ? TextHitInfo.leading(0) : TextHitInfo.trailing(characterCount-1);
2486             } else if (x >= bounds.getMaxX()) {
2487                 return isLeftToRight() ? TextHitInfo.trailing(characterCount-1) : TextHitInfo.leading(0);
2488             }
2489         }
2490 
2491         // revised hit test
2492         // the original seems too complex and fails miserably with italic offsets
2493         // the natural tendency is to move towards the character you want to hit
2494         // so we'll just measure distance to the center of each character's visual
2495         // bounds, pick the closest one, then see which side of the character's
2496         // center line (italic) the point is on.
2497         // this tends to make it easier to hit narrow characters, which can be a
2498         // bit odd if you're visually over an adjacent wide character. this makes
2499         // a difference with bidi, so perhaps i need to revisit this yet again.
2500 
2501         double distance = Double.MAX_VALUE;
2502         int index = 0;
2503         int trail = -1;
2504         CoreMetrics lcm = null;
2505         float icx = 0, icy = 0, ia = 0, cy = 0, dya = 0, ydsq = 0;
2506 
2507         for (int i = 0; i < characterCount; ++i) {
2508             if (!textLine.caretAtOffsetIsValid(i)) {
2509                 continue;
2510             }
2511             if (trail == -1) {
2512                 trail = i;
2513             }
2514             CoreMetrics cm = textLine.getCoreMetricsAt(i);
2515             if (cm != lcm) {
2516                 lcm = cm;
2517                 // just work around baseline mess for now
2518                 if (cm.baselineIndex == GraphicAttribute.TOP_ALIGNMENT) {
2519                     cy = -(textLine.getMetrics().ascent - cm.ascent) + cm.ssOffset;
2520                 } else if (cm.baselineIndex == GraphicAttribute.BOTTOM_ALIGNMENT) {
2521                     cy = textLine.getMetrics().descent - cm.descent + cm.ssOffset;
2522                 } else {
2523                     cy = cm.effectiveBaselineOffset(baselineOffsets) + cm.ssOffset;
2524                 }
2525                 float dy = (cm.descent - cm.ascent) / 2 - cy;
2526                 dya = dy * cm.italicAngle;
2527                 cy += dy;
2528                 ydsq = (cy - y)*(cy - y);
2529             }
2530             float cx = textLine.getCharXPosition(i);
2531             float ca = textLine.getCharAdvance(i);
2532             float dx = ca / 2;
2533             cx += dx - dya;
2534 
2535             // proximity in x (along baseline) is two times as important as proximity in y
2536             double nd = Math.sqrt(4*(cx - x)*(cx - x) + ydsq);
2537             if (nd < distance) {
2538                 distance = nd;
2539                 index = i;
2540                 trail = -1;
2541                 icx = cx; icy = cy; ia = cm.italicAngle;
2542             }
2543         }
2544         boolean left = x < icx - (y - icy) * ia;
2545         boolean leading = textLine.isCharLTR(index) == left;
2546         if (trail == -1) {
2547             trail = characterCount;
2548         }
2549         TextHitInfo result = leading ? TextHitInfo.leading(index) :
2550             TextHitInfo.trailing(trail-1);
2551         return result;
2552     }
2553 
2554     /**
2555      * Returns a <code>TextHitInfo</code> corresponding to the
2556      * specified point.  This method is a convenience overload of
2557      * <code>hitTestChar</code> that uses the natural bounds of this
2558      * <code>TextLayout</code>.
2559      * @param x the x offset from the origin of this
2560      *     <code>TextLayout</code>.  This is in standard coordinates.
2561      * @param y the y offset from the origin of this
2562      *     <code>TextLayout</code>.  This is in standard coordinates.
2563      * @return a hit describing the character and edge (leading or trailing)
2564      * under the specified point.
2565      */
2566     public TextHitInfo hitTestChar(float x, float y) {
2567 
2568         return hitTestChar(x, y, getNaturalBounds());
2569     }
2570 
2571     /**
2572      * Returns the hash code of this <code>TextLayout</code>.
2573      * @return the hash code of this <code>TextLayout</code>.
2574      */
2575     public int hashCode() {
2576         if (hashCodeCache == 0) {
2577             ensureCache();
2578             hashCodeCache = textLine.hashCode();
2579         }
2580         return hashCodeCache;
2581     }
2582 
2583     /**
2584      * Returns <code>true</code> if the specified <code>Object</code> is a
2585      * <code>TextLayout</code> object and if the specified <code>Object</code>
2586      * equals this <code>TextLayout</code>.
2587      * @param obj an <code>Object</code> to test for equality
2588      * @return <code>true</code> if the specified <code>Object</code>
2589      *      equals this <code>TextLayout</code>; <code>false</code>
2590      *      otherwise.
2591      */
2592     public boolean equals(Object obj) {
2593         return (obj instanceof TextLayout) && equals((TextLayout)obj);
2594     }
2595 
2596     /**
2597      * Returns <code>true</code> if the two layouts are equal.
2598      * Two layouts are equal if they contain equal glyphvectors in the same order.
2599      * @param rhs the <code>TextLayout</code> to compare to this
2600      *       <code>TextLayout</code>
2601      * @return <code>true</code> if the specified <code>TextLayout</code>
2602      *      equals this <code>TextLayout</code>.
2603      *
2604      */
2605     public boolean equals(TextLayout rhs) {
2606 
2607         if (rhs == null) {
2608             return false;
2609         }
2610         if (rhs == this) {
2611             return true;
2612         }
2613 
2614         ensureCache();
2615         return textLine.equals(rhs.textLine);
2616     }
2617 
2618     /**
2619      * Returns debugging information for this <code>TextLayout</code>.
2620      * @return the <code>textLine</code> of this <code>TextLayout</code>
2621      *        as a <code>String</code>.
2622      */
2623     public String toString() {
2624         ensureCache();
2625         return textLine.toString();
2626      }
2627 
2628     /**
2629      * Renders this <code>TextLayout</code> at the specified location in
2630      * the specified {@link java.awt.Graphics2D Graphics2D} context.
2631      * The origin of the layout is placed at x,&nbsp;y.  Rendering may touch
2632      * any point within <code>getBounds()</code> of this position.  This
2633      * leaves the <code>g2</code> unchanged.  Text is rendered along the
2634      * baseline path.
2635      * @param g2 the <code>Graphics2D</code> context into which to render
2636      *         the layout
2637      * @param x the X coordinate of the origin of this <code>TextLayout</code>
2638      * @param y the Y coordinate of the origin of this <code>TextLayout</code>
2639      * @see #getBounds()
2640      */
2641     public void draw(Graphics2D g2, float x, float y) {
2642 
2643         if (g2 == null) {
2644             throw new IllegalArgumentException("Null Graphics2D passed to TextLayout.draw()");
2645         }
2646 
2647         textLine.draw(g2, x - dx, y - dy);
2648     }
2649 
2650     /**
2651      * Package-only method for testing ONLY.  Please don't abuse.
2652      */
2653     TextLine getTextLineForTesting() {
2654 
2655         return textLine;
2656     }
2657 
2658     /**
2659      *
2660      * Return the index of the first character with a different baseline from the
2661      * character at start, or limit if all characters between start and limit have
2662      * the same baseline.
2663      */
2664     private static int sameBaselineUpTo(Font font, char[] text,
2665                                         int start, int limit) {
2666         // current implementation doesn't support multiple baselines
2667         return limit;
2668         /*
2669         byte bl = font.getBaselineFor(text[start++]);
2670         while (start < limit && font.getBaselineFor(text[start]) == bl) {
2671             ++start;
2672         }
2673         return start;
2674         */
2675     }
2676 
2677     static byte getBaselineFromGraphic(GraphicAttribute graphic) {
2678 
2679         byte alignment = (byte) graphic.getAlignment();
2680 
2681         if (alignment == GraphicAttribute.BOTTOM_ALIGNMENT ||
2682                 alignment == GraphicAttribute.TOP_ALIGNMENT) {
2683 
2684             return (byte)GraphicAttribute.ROMAN_BASELINE;
2685         }
2686         else {
2687             return alignment;
2688         }
2689     }
2690 
2691     /**
2692      * Returns a <code>Shape</code> representing the outline of this
2693      * <code>TextLayout</code>.
2694      * @param tx an optional {@link AffineTransform} to apply to the
2695      *     outline of this <code>TextLayout</code>.
2696      * @return a <code>Shape</code> that is the outline of this
2697      *     <code>TextLayout</code>.  This is in standard coordinates.
2698      */
2699     public Shape getOutline(AffineTransform tx) {
2700         ensureCache();
2701         Shape result = textLine.getOutline(tx);
2702         LayoutPathImpl lp = textLine.getLayoutPath();
2703         if (lp != null) {
2704             result = lp.mapShape(result);
2705         }
2706         return result;
2707     }
2708 
2709     /**
2710      * Return the LayoutPath, or null if the layout path is the
2711      * default path (x maps to advance, y maps to offset).
2712      * @return the layout path
2713      * @since 1.6
2714      */
2715     public LayoutPath getLayoutPath() {
2716         return textLine.getLayoutPath();
2717     }
2718 
2719    /**
2720      * Convert a hit to a point in standard coordinates.  The point is
2721      * on the baseline of the character at the leading or trailing
2722      * edge of the character, as appropriate.  If the path is
2723      * broken at the side of the character represented by the hit, the
2724      * point will be adjacent to the character.
2725      * @param hit the hit to check.  This must be a valid hit on
2726      * the TextLayout.
2727      * @param point the returned point. The point is in standard
2728      *     coordinates.
2729      * @throws IllegalArgumentException if the hit is not valid for the
2730      * TextLayout.
2731      * @throws NullPointerException if hit or point is null.
2732      * @since 1.6
2733      */
2734     public void hitToPoint(TextHitInfo hit, Point2D point) {
2735         if (hit == null || point == null) {
2736             throw new NullPointerException((hit == null ? "hit" : "point") +
2737                                            " can't be null");
2738         }
2739         ensureCache();
2740         checkTextHit(hit);
2741 
2742         float adv = 0;
2743         float off = 0;
2744 
2745         int ix = hit.getCharIndex();
2746         boolean leading = hit.isLeadingEdge();
2747         boolean ltr;
2748         if (ix == -1 || ix == textLine.characterCount()) {
2749             ltr = textLine.isDirectionLTR();
2750             adv = (ltr == (ix == -1)) ? 0 : lineMetrics.advance;
2751         } else {
2752             ltr = textLine.isCharLTR(ix);
2753             adv = textLine.getCharLinePosition(ix, leading);
2754             off = textLine.getCharYPosition(ix);
2755         }
2756         point.setLocation(adv, off);
2757         LayoutPath lp = textLine.getLayoutPath();
2758         if (lp != null) {
2759             lp.pathToPoint(point, ltr != leading, point);
2760         }
2761     }
2762 }
--- EOF ---