View Javadoc
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.CoreMetrics;
64  import sun.font.Decoration;
65  import sun.font.FontLineMetrics;
66  import sun.font.FontResolver;
67  import sun.font.GraphicComponent;
68  import sun.font.LayoutPathImpl;
69  import sun.text.CodePointIterator;
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 }