KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > java > awt > font > TextLayout


1 /*
2  * @(#)TextLayout.java 1.97 04/05/05
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 /*
9  * (C) Copyright Taligent, Inc. 1996 - 1997, All Rights Reserved
10  * (C) Copyright IBM Corp. 1996-2003, All Rights Reserved
11  *
12  * The original version of this source code and documentation is
13  * copyrighted and owned by Taligent, Inc., a wholly-owned subsidiary
14  * of IBM. These materials are provided under terms of a License
15  * Agreement between Taligent and Sun. This technology is protected
16  * by multiple US and International patents.
17  *
18  * This notice and attribution to Taligent may not be removed.
19  * Taligent is a registered trademark of Taligent, Inc.
20  *
21  */

22
23 package java.awt.font;
24
25 import java.awt.Color JavaDoc;
26 import java.awt.Font JavaDoc;
27 import java.awt.Graphics2D JavaDoc;
28 import java.awt.Shape JavaDoc;
29 import java.awt.font.NumericShaper JavaDoc;
30 import java.awt.font.TextLine.TextLineMetrics JavaDoc;
31 import java.awt.geom.AffineTransform JavaDoc;
32 import java.awt.geom.GeneralPath JavaDoc;
33 import java.awt.geom.Point2D JavaDoc;
34 import java.awt.geom.Rectangle2D JavaDoc;
35 import java.text.AttributedString JavaDoc;
36 import java.text.AttributedCharacterIterator JavaDoc;
37 import java.text.AttributedCharacterIterator.Attribute;
38 import java.util.Map JavaDoc;
39 import java.util.HashMap JavaDoc;
40 import java.util.Hashtable JavaDoc;
41 import sun.font.AdvanceCache;
42 import sun.font.CoreMetrics;
43 import sun.font.Decoration;
44 import sun.font.FontLineMetrics;
45 import sun.font.FontResolver;
46 import sun.font.GraphicComponent;
47 import sun.text.CodePointIterator;
48
49 /**
50  *
51  * <code>TextLayout</code> is an immutable graphical representation of styled
52  * character data.
53  * <p>
54  * It provides the following capabilities:
55  * <ul>
56  * <li>implicit bidirectional analysis and reordering,
57  * <li>cursor positioning and movement, including split cursors for
58  * mixed directional text,
59  * <li>highlighting, including both logical and visual highlighting
60  * for mixed directional text,
61  * <li>multiple baselines (roman, hanging, and centered),
62  * <li>hit testing,
63  * <li>justification,
64  * <li>default font substitution,
65  * <li>metric information such as ascent, descent, and advance, and
66  * <li>rendering
67  * </ul>
68  * <p>
69  * A <code>TextLayout</code> object can be rendered using
70  * its <code>draw</code> method.
71  * <p>
72  * <code>TextLayout</code> can be constructed either directly or through
73  * the use of a {@link LineBreakMeasurer}. When constructed directly, the
74  * source text represents a single paragraph. <code>LineBreakMeasurer</code>
75  * allows styled text to be broken into lines that fit within a particular
76  * width. See the <code>LineBreakMeasurer</code> documentation for more
77  * information.
78  * <p>
79  * <code>TextLayout</code> construction logically proceeds as follows:
80  * <ul>
81  * <li>paragraph attributes are extracted and examined,
82  * <li>text is analyzed for bidirectional reordering, and reordering
83  * information is computed if needed,
84  * <li>text is segmented into style runs
85  * <li>fonts are chosen for style runs, first by using a font if the
86  * attribute {@link TextAttribute#FONT} is present, otherwise by computing
87  * a default font using the attributes that have been defined
88  * <li>if text is on multiple baselines, the runs or subruns are further
89  * broken into subruns sharing a common baseline,
90  * <li>glyphvectors are generated for each run using the chosen font,
91  * <li>final bidirectional reordering is performed on the glyphvectors
92  * </ul>
93  * <p>
94  * All graphical information returned from a <code>TextLayout</code>
95  * object's methods is relative to the origin of the
96  * <code>TextLayout</code>, which is the intersection of the
97  * <code>TextLayout</code> object's baseline with its left edge. Also,
98  * coordinates passed into a <code>TextLayout</code> object's methods
99  * are assumed to be relative to the <code>TextLayout</code> object's
100  * origin. Clients usually need to translate between a
101  * <code>TextLayout</code> object's coordinate system and the coordinate
102  * system in another object (such as a
103  * {@link java.awt.Graphics Graphics} object).
104  * <p>
105  * <code>TextLayout</code> objects are constructed from styled text,
106  * but they do not retain a reference to their source text. Thus,
107  * changes in the text previously used to generate a <code>TextLayout</code>
108  * do not affect the <code>TextLayout</code>.
109  * <p>
110  * Three methods on a <code>TextLayout</code> object
111  * (<code>getNextRightHit</code>, <code>getNextLeftHit</code>, and
112  * <code>hitTestChar</code>) return instances of {@link TextHitInfo}.
113  * The offsets contained in these <code>TextHitInfo</code> objects
114  * are relative to the start of the <code>TextLayout</code>, <b>not</b>
115  * to the text used to create the <code>TextLayout</code>. Similarly,
116  * <code>TextLayout</code> methods that accept <code>TextHitInfo</code>
117  * instances as parameters expect the <code>TextHitInfo</code> object's
118  * offsets to be relative to the <code>TextLayout</code>, not to any
119  * underlying text storage model.
120  * <p>
121  * <strong>Examples</strong>:<p>
122  * Constructing and drawing a <code>TextLayout</code> and its bounding
123  * rectangle:
124  * <blockquote><pre>
125  * Graphics2D g = ...;
126  * Point2D loc = ...;
127  * Font font = Font.getFont("Helvetica-bold-italic");
128  * FontRenderContext frc = g.getFontRenderContext();
129  * TextLayout layout = new TextLayout("This is a string", font, frc);
130  * layout.draw(g, (float)loc.getX(), (float)loc.getY());
131  *
132  * Rectangle2D bounds = layout.getBounds();
133  * bounds.setRect(bounds.getX()+loc.getX(),
134  * bounds.getY()+loc.getY(),
135  * bounds.getWidth(),
136  * bounds.getHeight());
137  * g.draw(bounds);
138  * </pre>
139  * </blockquote>
140  * <p>
141  * Hit-testing a <code>TextLayout</code> (determining which character is at
142  * a particular graphical location):
143  * <blockquote><pre>
144  * Point2D click = ...;
145  * TextHitInfo hit = layout.hitTestChar(
146  * (float) (click.getX() - loc.getX()),
147  * (float) (click.getY() - loc.getY()));
148  * </pre>
149  * </blockquote>
150  * <p>
151  * Responding to a right-arrow key press:
152  * <blockquote><pre>
153  * int insertionIndex = ...;
154  * TextHitInfo next = layout.getNextRightHit(insertionIndex);
155  * if (next != null) {
156  * // translate graphics to origin of layout on screen
157  * g.translate(loc.getX(), loc.getY());
158  * Shape[] carets = layout.getCaretShapes(next.getInsertionIndex());
159  * g.draw(carets[0]);
160  * if (carets[1] != null) {
161  * g.draw(carets[1]);
162  * }
163  * }
164  * </pre></blockquote>
165  * <p>
166  * Drawing a selection range corresponding to a substring in the source text.
167  * The selected area may not be visually contiguous:
168  * <blockquote><pre>
169  * // selStart, selLimit should be relative to the layout,
170  * // not to the source text
171  *
172  * int selStart = ..., selLimit = ...;
173  * Color selectionColor = ...;
174  * Shape selection = layout.getLogicalHighlightShape(selStart, selLimit);
175  * // selection may consist of disjoint areas
176  * // graphics is assumed to be tranlated to origin of layout
177  * g.setColor(selectionColor);
178  * g.fill(selection);
179  * </pre></blockquote>
180  * <p>
181  * Drawing a visually contiguous selection range. The selection range may
182  * correspond to more than one substring in the source text. The ranges of
183  * the corresponding source text substrings can be obtained with
184  * <code>getLogicalRangesForVisualSelection()</code>:
185  * <blockquote><pre>
186  * TextHitInfo selStart = ..., selLimit = ...;
187  * Shape selection = layout.getVisualHighlightShape(selStart, selLimit);
188  * g.setColor(selectionColor);
189  * g.fill(selection);
190  * int[] ranges = getLogicalRangesForVisualSelection(selStart, selLimit);
191  * // ranges[0], ranges[1] is the first selection range,
192  * // ranges[2], ranges[3] is the second selection range, etc.
193  * </pre></blockquote>
194  * <p>
195  * @see LineBreakMeasurer
196  * @see TextAttribute
197  * @see TextHitInfo
198  */

199 public final class TextLayout implements Cloneable JavaDoc {
200
201     private int characterCount;
202     private boolean isVerticalLine = false;
203     private byte baseline;
204     private float[] baselineOffsets; // why have these ?
205
private TextLine JavaDoc textLine;
206
207     // cached values computed from GlyphSets and set info:
208
// all are recomputed from scratch in buildCache()
209
private TextLine.TextLineMetrics JavaDoc lineMetrics = null;
210     private float visibleAdvance;
211     private int hashCodeCache;
212
213     /**
214      * temporary optimization
215      */

216     static class OptInfo implements Decoration.Label {
217         private static final float MAGIC_ADVANCE = -12345.67f;
218
219         // cache of required information for TextLine construction
220
private FontRenderContext JavaDoc frc;
221         private char[] chars;
222         private Font JavaDoc font;
223         private CoreMetrics metrics;
224         private Map JavaDoc attrs;
225
226         // deferred initialization
227
private float advance;
228     private Rectangle2D JavaDoc vb;
229         private Decoration decoration;
230         private String JavaDoc str;
231
232         private OptInfo(FontRenderContext JavaDoc frc, char[] chars, Font JavaDoc font, CoreMetrics metrics, Map JavaDoc attrs) {
233             this.frc = frc;
234             this.chars = chars;
235             this.font = font;
236             this.metrics = metrics;
237             this.attrs = attrs;
238
239         if (attrs != null) {
240         this.attrs = new HashMap JavaDoc(attrs); // sigh, need to clone since might change...
241
}
242
243             this.advance = MAGIC_ADVANCE;
244         }
245
246         TextLine JavaDoc createTextLine() {
247             return TextLine.fastCreateTextLine(frc, chars, font, metrics, attrs);
248         }
249
250         float getAdvance() {
251             if (advance == MAGIC_ADVANCE) {
252                 AdvanceCache adv = AdvanceCache.get(font, frc);
253                 advance = adv.getAdvance(chars, 0, chars.length); // we pretested the chars array so no exception here
254
}
255             return advance;
256         }
257
258         // Decoration.Label reqd.
259
public CoreMetrics getCoreMetrics() {
260             return metrics;
261         }
262
263         // Decoration.Label reqd.
264
public Rectangle2D JavaDoc getLogicalBounds() {
265             return new Rectangle2D.Float JavaDoc(0, -metrics.ascent, getAdvance(), metrics.height);
266         }
267
268         // Decoration.Label reqd.
269
public void handleDraw(Graphics2D JavaDoc g2d, float x, float y) {
270             if (str == null) {
271                 str = new String JavaDoc(chars, 0, chars.length);
272             }
273             g2d.drawString(str, x , y);
274         }
275
276         // Decoration.Label reqd.
277
public Rectangle2D JavaDoc handleGetCharVisualBounds(int index) {
278             // not used
279
throw new InternalError JavaDoc();
280         }
281
282         // Decoration.Label reqd.
283
public Rectangle2D JavaDoc handleGetVisualBounds() {
284         AdvanceCache adv = AdvanceCache.get(font, frc);
285         return adv.getVisualBounds(chars, 0, chars.length);
286         }
287
288         // Decoration.Label reqd.
289
public Shape JavaDoc handleGetOutline(float x, float y) {
290             // not used
291
throw new InternalError JavaDoc();
292         }
293
294         // if we could successfully draw, then return true
295
boolean draw(Graphics2D JavaDoc g2d, float x, float y) {
296             // If the frc differs from the graphics frc, we punt to TextLayout because the
297
// metrics might be different...
298
if (g2d.getFontRenderContext().equals(frc)) {
299                 Font JavaDoc oldFont = g2d.getFont();
300                 g2d.setFont(font);
301                     
302                 getDecoration().drawTextAndDecorations(this, g2d, x, y);
303
304                 g2d.setFont(oldFont);
305
306                 return true;
307             }
308             return false;
309         }
310
311     Rectangle2D JavaDoc getVisualBounds() {
312         if (vb == null) {
313         vb = getDecoration().getVisualBounds(this);
314         }
315         return (Rectangle2D JavaDoc)vb.clone();
316     }
317
318     Decoration getDecoration() {
319         if (decoration == null) {
320         if (attrs == null) {
321             decoration = Decoration.getDecoration(null);
322         } else {
323             decoration = Decoration.getDecoration(StyledParagraph.addInputMethodAttrs(attrs));
324         }
325         }
326         return decoration;
327     }
328
329         static OptInfo create(FontRenderContext JavaDoc frc, char[] chars, Font JavaDoc font, CoreMetrics metrics, Map JavaDoc attrs) {
330             // Preflight text to make sure advance cache supports it, otherwise it would throw an exception.
331
// We also need to preflight to make sure we don't require layout. If we limit optimizations to
332
// latin-1 we handle both cases. We could add an additional check for Japanese since currently
333
// it doesn't require layout and the advance cache would be simple, but right now we don't.
334

335             if (!font.isTransformed() && AdvanceCache.supportsText(chars)) {
336         if (attrs == null || attrs.get(TextAttribute.CHAR_REPLACEMENT) == null) {
337             return new OptInfo(frc, chars, font, metrics, attrs);
338         }
339             }
340             return null;
341         }
342     }
343     private OptInfo optInfo;
344
345     /*
346      * TextLayouts are supposedly immutable. If you mutate a TextLayout under
347      * the covers (like the justification code does) you'll need to set this
348      * back to false. Could be replaced with textLine != null <--> cacheIsValid.
349      */

350     private boolean cacheIsValid = false;
351
352
353     // This value is obtained from an attribute, and constrained to the
354
// interval [0,1]. If 0, the layout cannot be justified.
355
private float justifyRatio;
356
357     // If a layout is produced by justification, then that layout
358
// cannot be justified. To enforce this constraint the
359
// justifyRatio of the justified layout is set to this value.
360
private static final float ALREADY_JUSTIFIED = -53.9f;
361
362     // dx and dy specify the distance between the TextLayout's origin
363
// and the origin of the leftmost GlyphSet (TextLayoutComponent,
364
// actually). They were used for hanging punctuation support,
365
// which is no longer implemented. Currently they are both always 0,
366
// and TextLayout is not guaranteed to work with non-zero dx, dy
367
// values right now. They were left in as an aide and reminder to
368
// anyone who implements hanging punctuation or other similar stuff.
369
// They are static now so they don't take up space in TextLayout
370
// instances.
371
private static float dx;
372     private static float dy;
373
374     /*
375      * Natural bounds is used internally. It is built on demand in
376      * getNaturalBounds.
377      */

378     private Rectangle2D JavaDoc naturalBounds = null;
379
380     /*
381      * boundsRect encloses all of the bits this TextLayout can draw. It
382      * is build on demand in getBounds.
383      */

384     private Rectangle2D JavaDoc boundsRect = null;
385
386     /*
387      * flag to supress/allow carets inside of ligatures when hit testing or
388      * arrow-keying
389      */

390     private boolean caretsInLigaturesAreAllowed = false;
391
392     /**
393      * Defines a policy for determining the strong caret location.
394      * This class contains one method, <code>getStrongCaret</code>, which
395      * is used to specify the policy that determines the strong caret in
396      * dual-caret text. The strong caret is used to move the caret to the
397      * left or right. Instances of this class can be passed to
398      * <code>getCaretShapes</code>, <code>getNextLeftHit</code> and
399      * <code>getNextRightHit</code> to customize strong caret
400      * selection.
401      * <p>
402      * To specify alternate caret policies, subclass <code>CaretPolicy</code>
403      * and override <code>getStrongCaret</code>. <code>getStrongCaret</code>
404      * should inspect the two <code>TextHitInfo</code> arguments and choose
405      * one of them as the strong caret.
406      * <p>
407      * Most clients do not need to use this class.
408      */

409     public static class CaretPolicy {
410
411         /**
412          * Constructs a <code>CaretPolicy</code>.
413          */

414          public CaretPolicy() {
415          }
416
417         /**
418          * Chooses one of the specified <code>TextHitInfo</code> instances as
419          * a strong caret in the specified <code>TextLayout</code>.
420          * @param hit1 a valid hit in <code>layout</code>
421          * @param hit2 a valid hit in <code>layout</code>
422          * @param layout the <code>TextLayout</code> in which
423          * <code>hit1</code> and <code>hit2</code> are used
424          * @return <code>hit1</code> or <code>hit2</code>
425          * (or an equivalent <code>TextHitInfo</code>), indicating the
426          * strong caret.
427          */

428         public TextHitInfo JavaDoc getStrongCaret(TextHitInfo JavaDoc hit1,
429                                           TextHitInfo JavaDoc hit2,
430                                           TextLayout JavaDoc layout) {
431
432             // default implmentation just calls private method on layout
433
return layout.getStrongHit(hit1, hit2);
434         }
435     }
436
437     /**
438      * This <code>CaretPolicy</code> is used when a policy is not specified
439      * by the client. With this policy, a hit on a character whose direction
440      * is the same as the line direction is stronger than a hit on a
441      * counterdirectional character. If the characters' directions are
442      * the same, a hit on the leading edge of a character is stronger
443      * than a hit on the trailing edge of a character.
444      */

445     public static final CaretPolicy DEFAULT_CARET_POLICY = new CaretPolicy();
446
447     /**
448      * Constructs a <code>TextLayout</code> from a <code>String</code>
449      * and a {@link Font}. All the text is styled using the specified
450      * <code>Font</code>.
451      * <p>
452      * The <code>String</code> must specify a single paragraph of text,
453      * because an entire paragraph is required for the bidirectional
454      * algorithm.
455      * @param string the text to display
456      * @param font a <code>Font</code> used to style the text
457      * @param frc contains information about a graphics device which is needed
458      * to measure the text correctly.
459      * Text measurements can vary slightly depending on the
460      * device resolution, and attributes such as antialiasing. This
461      * parameter does not specify a translation between the
462      * <code>TextLayout</code> and user space.
463      */

464     public TextLayout(String JavaDoc string, Font JavaDoc font, FontRenderContext JavaDoc frc) {
465
466         if (font == null) {
467             throw new IllegalArgumentException JavaDoc("Null font passed to TextLayout constructor.");
468         }
469
470         if (string == null) {
471             throw new IllegalArgumentException JavaDoc("Null string passed to TextLayout constructor.");
472         }
473
474         if (string.length() == 0) {
475             throw new IllegalArgumentException JavaDoc("Zero length string passed to TextLayout constructor.");
476         }
477
478         char[] text = string.toCharArray();
479         if (sameBaselineUpTo(font, text, 0, text.length) == text.length) {
480             fastInit(text, font, null, frc);
481         } else {
482             AttributedString JavaDoc as = new AttributedString JavaDoc(string);
483             as.addAttribute(TextAttribute.FONT, font);
484             standardInit(as.getIterator(), text, frc);
485         }
486     }
487
488     /**
489      * Constructs a <code>TextLayout</code> from a <code>String</code>
490      * and an attribute set.
491      * <p>
492      * All the text is styled using the provided attributes.
493      * <p>
494      * <code>string</code> must specify a single paragraph of text because an
495      * entire paragraph is required for the bidirectional algorithm.
496      * @param string the text to display
497      * @param attributes the attributes used to style the text
498      * @param frc contains information about a graphics device which is needed
499      * to measure the text correctly.
500      * Text measurements can vary slightly depending on the
501      * device resolution, and attributes such as antialiasing. This
502      * parameter does not specify a translation between the
503      * <code>TextLayout</code> and user space.
504      */

505     public TextLayout(String JavaDoc string, Map JavaDoc<? extends Attribute,?> attributes,
506               FontRenderContext JavaDoc frc)
507     {
508
509         if (string == null) {
510             throw new IllegalArgumentException JavaDoc("Null string passed to TextLayout constructor.");
511         }
512
513         if (attributes == null) {
514             throw new IllegalArgumentException JavaDoc("Null map passed to TextLayout constructor.");
515         }
516
517         if (string.length() == 0) {
518             throw new IllegalArgumentException JavaDoc("Zero length string passed to TextLayout constructor.");
519         }
520
521         char[] text = string.toCharArray();
522         Font JavaDoc font = singleFont(text, 0, text.length, attributes);
523         if (font != null) {
524             fastInit(text, font, attributes, frc);
525         } else {
526             AttributedString JavaDoc as = new AttributedString JavaDoc(string, attributes);
527             standardInit(as.getIterator(), text, frc);
528         }
529     }
530
531     /*
532      * Determines a font for the attributes, and if a single font can render
533      * all the text on one baseline, return it, otherwise null. If the
534      * attributes specify a font, assume it can display all the text without
535      * checking.
536      * If the AttributeSet contains an embedded graphic, return null.
537      */

538     private static Font JavaDoc singleFont(char[] text,
539                                    int start,
540                                    int limit,
541                                    Map JavaDoc attributes) {
542
543         if (attributes.get(TextAttribute.CHAR_REPLACEMENT) != null) {
544             return null;
545         }
546
547         Font JavaDoc font = (Font JavaDoc)attributes.get(TextAttribute.FONT);
548         if (font == null) {
549             if (attributes.get(TextAttribute.FAMILY) != null) {
550                 font = Font.getFont(attributes);
551                 if (font.canDisplayUpTo(text, start, limit) != -1) {
552                     return null;
553                 }
554             } else {
555                 FontResolver resolver = FontResolver.getInstance();
556         CodePointIterator iter = CodePointIterator.create(text, start, limit);
557         int fontIndex = resolver.nextFontRunIndex(iter);
558         if (iter.charIndex() == limit) {
559             font = resolver.getFont(fontIndex, attributes);
560         }
561             }
562         }
563
564         if (sameBaselineUpTo(font, text, start, limit) != limit) {
565             return null;
566         }
567
568         return font;
569     }
570
571     /**
572      * Constructs a <code>TextLayout</code> from an iterator over styled text.
573      * <p>
574      * The iterator must specify a single paragraph of text because an
575      * entire paragraph is required for the bidirectional
576      * algorithm.
577      * @param text the styled text to display
578      * @param frc contains information about a graphics device which is needed
579      * to measure the text correctly.
580      * Text measurements can vary slightly depending on the
581      * device resolution, and attributes such as antialiasing. This
582      * parameter does not specify a translation between the
583      * <code>TextLayout</code> and user space.
584      */

585     public TextLayout(AttributedCharacterIterator JavaDoc text, FontRenderContext JavaDoc frc) {
586
587         if (text == null) {
588             throw new IllegalArgumentException JavaDoc("Null iterator passed to TextLayout constructor.");
589         }
590
591         int start = text.getBeginIndex();
592         int limit = text.getEndIndex();
593         if (start == limit) {
594             throw new IllegalArgumentException JavaDoc("Zero length iterator passed to TextLayout constructor.");
595         }
596
597         int len = limit - start;
598         text.first();
599         char[] chars = new char[len];
600         int n = 0;
601         for (char c = text.first(); c != text.DONE; c = text.next()) {
602             chars[n++] = c;
603         }
604
605         text.first();
606         if (text.getRunLimit() == limit) {
607
608             Map JavaDoc attributes = text.getAttributes();
609             Font JavaDoc font = singleFont(chars, 0, len, attributes);
610             if (font != null) {
611                 fastInit(chars, font, attributes, frc);
612                 return;
613             }
614         }
615
616         standardInit(text, chars, frc);
617     }
618
619     /**
620      * Creates a <code>TextLayout</code> from a {@link TextLine} and
621      * some paragraph data. This method is used by {@link TextMeasurer}.
622      * @param textLine the line measurement attributes to apply to the
623      * the resulting <code>TextLayout</code>
624      * @param baseline the baseline of the text
625      * @param baselineOffsets the baseline offsets for this
626      * <code>TextLayout</code>. This should already be normalized to
627      * <code>baseline</code>
628      * @param justifyRatio <code>0</code> if the <code>TextLayout</code>
629      * cannot be justified; <code>1</code> otherwise.
630      */

631     TextLayout(TextLine JavaDoc textLine,
632                byte baseline,
633                float[] baselineOffsets,
634                float justifyRatio) {
635
636         this.characterCount = textLine.characterCount();
637         this.baseline = baseline;
638         this.baselineOffsets = baselineOffsets;
639         this.textLine = textLine;
640         this.justifyRatio = justifyRatio;
641     }
642
643     /**
644      * Initialize the paragraph-specific data.
645      */

646     private void paragraphInit(byte aBaseline, CoreMetrics lm, Map JavaDoc paragraphAttrs, char[] text) {
647         
648         baseline = aBaseline;
649
650         // normalize to current baseline
651
baselineOffsets = TextLine.getNormalizedOffsets(lm.baselineOffsets, baseline);
652         
653         justifyRatio = TextLine.getJustifyRatio(paragraphAttrs);
654
655     if (paragraphAttrs != null) {
656         Object JavaDoc o = paragraphAttrs.get(TextAttribute.NUMERIC_SHAPING);
657         if (o != null) {
658         try {
659           NumericShaper JavaDoc shaper = (NumericShaper JavaDoc)o;
660           shaper.shape(text, 0, text.length);
661         }
662         catch (ClassCastException JavaDoc e) {
663         }
664         }
665     }
666     }
667
668     /*
669      * the fast init generates a single glyph set. This requires:
670      * all one style
671      * all renderable by one font (ie no embedded graphics)
672      * all on one baseline
673      */

674     private void fastInit(char[] chars, Font JavaDoc font, Map JavaDoc attrs, FontRenderContext JavaDoc frc) {
675         // Object vf = attrs.get(TextAttribute.ORIENTATION);
676
// isVerticalLine = TextAttribute.ORIENTATION_VERTICAL.equals(vf);
677
isVerticalLine = false;
678
679         LineMetrics JavaDoc lm = font.getLineMetrics(chars, 0, chars.length, frc);
680         CoreMetrics cm = CoreMetrics.get(lm);
681         byte glyphBaseline = (byte) cm.baselineIndex;
682
683         if (attrs == null) {
684             baseline = glyphBaseline;
685             baselineOffsets = cm.baselineOffsets;
686             justifyRatio = 1.0f;
687         } else {
688             paragraphInit(glyphBaseline, cm, attrs, chars);
689         }
690
691         characterCount = chars.length;
692
693         optInfo = OptInfo.create(frc, chars, font, cm, attrs);
694         if (optInfo == null) {
695             textLine = TextLine.fastCreateTextLine(frc, chars, font, cm, attrs);
696         }
697     }
698
699     private void initTextLine() {
700         textLine = optInfo.createTextLine();
701         optInfo = null;
702     }
703
704     /*
705      * the standard init generates multiple glyph sets based on style,
706      * renderable, and baseline runs.
707      * @param chars the text in the iterator, extracted into a char array
708      */

709     private void standardInit(AttributedCharacterIterator JavaDoc text, char[] chars, FontRenderContext JavaDoc frc) {
710
711         characterCount = chars.length;
712
713         // set paragraph attributes
714
{
715             // If there's an embedded graphic at the start of the
716
// paragraph, look for the first non-graphic character
717
// and use it and its font to initialize the paragraph.
718
// If not, use the first graphic to initialize.
719

720             Map JavaDoc paragraphAttrs = text.getAttributes();
721
722             boolean haveFont = TextLine.advanceToFirstFont(text);
723
724             if (haveFont) {
725                 Font JavaDoc defaultFont = TextLine.getFontAtCurrentPos(text);
726                 int charsStart = text.getIndex() - text.getBeginIndex();
727                 LineMetrics JavaDoc lm = defaultFont.getLineMetrics(chars, charsStart, charsStart+1, frc);
728                 CoreMetrics cm = CoreMetrics.get(lm);
729                 paragraphInit((byte)cm.baselineIndex, cm, paragraphAttrs, chars);
730             }
731             else {
732                 // hmmm what to do here? Just try to supply reasonable
733
// values I guess.
734

735                 GraphicAttribute JavaDoc graphic = (GraphicAttribute JavaDoc)
736                                 paragraphAttrs.get(TextAttribute.CHAR_REPLACEMENT);
737                 byte defaultBaseline = getBaselineFromGraphic(graphic);
738         CoreMetrics cm = GraphicComponent.createCoreMetrics(graphic);
739                 paragraphInit(defaultBaseline, cm, paragraphAttrs, chars);
740             }
741         }
742
743         textLine = TextLine.standardCreateTextLine(frc, text, chars, baselineOffsets);
744     }
745
746     /*
747      * A utility to rebuild the ascent/descent/leading/advance cache.
748      * You'll need to call this if you clone and mutate (like justification,
749      * editing methods do)
750      */

751     private void ensureCache() {
752         if (!cacheIsValid) {
753             buildCache();
754         }
755     }
756
757     private void buildCache() {
758         if (textLine == null) {
759             initTextLine();
760         }
761
762         lineMetrics = textLine.getMetrics();
763
764         // compute visibleAdvance
765
if (textLine.isDirectionLTR()) {
766
767             int lastNonSpace = characterCount-1;
768             while (lastNonSpace != -1) {
769                 int logIndex = textLine.visualToLogical(lastNonSpace);
770                 if (!textLine.isCharSpace(logIndex)) {
771                     break;
772                 }
773                 else {
774                     --lastNonSpace;
775                 }
776             }
777             if (lastNonSpace == characterCount-1) {
778                 visibleAdvance = lineMetrics.advance;
779             }
780             else if (lastNonSpace == -1) {
781                 visibleAdvance = 0;
782             }
783             else {
784                 int logIndex = textLine.visualToLogical(lastNonSpace);
785                 visibleAdvance = textLine.getCharLinePosition(logIndex)
786                                         + textLine.getCharAdvance(logIndex);
787             }
788         }
789         else {
790
791             int leftmostNonSpace = 0;
792             while (leftmostNonSpace != characterCount) {
793                 int logIndex = textLine.visualToLogical(leftmostNonSpace);
794                 if (!textLine.isCharSpace(logIndex)) {
795                     break;
796                 }
797                 else {
798                     ++leftmostNonSpace;
799                 }
800             }
801             if (leftmostNonSpace == characterCount) {
802                 visibleAdvance = 0;
803             }
804             else if (leftmostNonSpace == 0) {
805                 visibleAdvance = lineMetrics.advance;
806             }
807             else {
808                 int logIndex = textLine.visualToLogical(leftmostNonSpace);
809                 float pos = textLine.getCharLinePosition(logIndex);
810                 visibleAdvance = lineMetrics.advance - pos;
811             }
812         }
813
814         // naturalBounds, boundsRect will be generated on demand
815
naturalBounds = null;
816         boundsRect = null;
817
818         // hashCode will be regenerated on demand
819
hashCodeCache = 0;
820
821         cacheIsValid = true;
822     }
823
824     /**
825      * The 'natural bounds' encloses all the carets the layout can draw.
826      *
827      */

828     private Rectangle2D JavaDoc getNaturalBounds() {
829         ensureCache();
830
831         if (naturalBounds == null) {
832             naturalBounds = textLine.getItalicBounds();
833         }
834
835         return naturalBounds;
836     }
837
838     /**
839      * Creates a copy of this <code>TextLayout</code>.
840      */

841     protected Object JavaDoc clone() {
842         /*
843          * !!! I think this is safe. Once created, nothing mutates the
844          * glyphvectors or arrays. But we need to make sure.
845          * {jbr} actually, that's not quite true. The justification code
846          * mutates after cloning. It doesn't actually change the glyphvectors
847          * (that's impossible) but it replaces them with justified sets. This
848          * is a problem for GlyphIterator creation, since new GlyphIterators
849          * are created by cloning a prototype. If the prototype has outdated
850          * glyphvectors, so will the new ones. A partial solution is to set the
851          * prototypical GlyphIterator to null when the glyphvectors change. If
852          * you forget this one time, you're hosed.
853          */

854         try {
855             return super.clone();
856         }
857         catch (CloneNotSupportedException JavaDoc e) {
858             throw new InternalError JavaDoc();
859         }
860     }
861
862     /*
863      * Utility to throw an expection if an invalid TextHitInfo is passed
864      * as a parameter. Avoids code duplication.
865      */

866     private void checkTextHit(TextHitInfo JavaDoc hit) {
867         if (hit == null) {
868             throw new IllegalArgumentException JavaDoc("TextHitInfo is null.");
869         }
870
871         if (hit.getInsertionIndex() < 0 ||
872             hit.getInsertionIndex() > characterCount) {
873             throw new IllegalArgumentException JavaDoc("TextHitInfo is out of range");
874         }
875     }
876
877     /**
878      * Creates a copy of this <code>TextLayout</code> justified to the
879      * specified width.
880      * <p>
881      * If this <code>TextLayout</code> has already been justified, an
882      * exception is thrown. If this <code>TextLayout</code> object's
883      * justification ratio is zero, a <code>TextLayout</code> identical
884      * to this <code>TextLayout</code> is returned.
885      * @param justificationWidth the width to use when justifying the line.
886      * For best results, it should not be too different from the current
887      * advance of the line.
888      * @return a <code>TextLayout</code> justified to the specified width.
889      * @exception Error if this layout has already been justified, an Error is
890      * thrown.
891      */

892     public TextLayout JavaDoc getJustifiedLayout(float justificationWidth) {
893
894         if (justificationWidth <= 0) {
895             throw new IllegalArgumentException JavaDoc("justificationWidth <= 0 passed to TextLayout.getJustifiedLayout()");
896         }
897
898         if (justifyRatio == ALREADY_JUSTIFIED) {
899             throw new Error JavaDoc("Can't justify again.");
900         }
901
902     ensureCache(); // make sure textLine is not null
903

904         // default justification range to exclude trailing logical whitespace
905
int limit = characterCount;
906         while (limit > 0 && textLine.isCharWhitespace(limit-1)) {
907             --limit;
908         }
909
910         TextLine JavaDoc newLine = textLine.getJustifiedLine(justificationWidth, justifyRatio, 0, limit);
911         if (newLine != null) {
912             return new TextLayout JavaDoc(newLine, baseline, baselineOffsets, ALREADY_JUSTIFIED);
913         }
914
915         return this;
916     }
917
918     /**
919      * Justify this layout. Overridden by subclassers to control justification
920      * (if there were subclassers, that is...)
921      *
922      * The layout will only justify if the paragraph attributes (from the
923      * source text, possibly defaulted by the layout attributes) indicate a
924      * non-zero justification ratio. The text will be justified to the
925      * indicated width. The current implementation also adjusts hanging
926      * punctuation and trailing whitespace to overhang the justification width.
927      * Once justified, the layout may not be rejustified.
928      * <p>
929      * Some code may rely on immutablity of layouts. Subclassers should not
930      * call this directly, but instead should call getJustifiedLayout, which
931      * will call this method on a clone of this layout, preserving
932      * the original.
933      *
934      * @param justificationWidth the width to use when justifying the line.
935      * For best results, it should not be too different from the current
936      * advance of the line.
937      * @see #getJustifiedLayout(float)
938      */

939     protected void handleJustify(float justificationWidth) {
940       // never called
941
}
942
943
944     /**
945      * Returns the baseline for this <code>TextLayout</code>.
946      * The baseline is one of the values defined in <code>Font</code>,
947      * which are roman, centered and hanging. Ascent and descent are
948      * relative to this baseline. The <code>baselineOffsets</code>
949      * are also relative to this baseline.
950      * @return the baseline of this <code>TextLayout</code>.
951      * @see #getBaselineOffsets()
952      * @see Font
953      */

954     public byte getBaseline() {
955         return baseline;
956     }
957
958     /**
959      * Returns the offsets array for the baselines used for this
960      * <code>TextLayout</code>.
961      * <p>
962      * The array is indexed by one of the values defined in
963      * <code>Font</code>, which are roman, centered and hanging. The
964      * values are relative to this <code>TextLayout</code> object's
965      * baseline, so that <code>getBaselineOffsets[getBaseline()] == 0</code>.
966      * Offsets are added to the position of the <code>TextLayout</code>
967      * object's baseline to get the position for the new baseline.
968      * @return the offsets array containing the baselines used for this
969      * <code>TextLayout</code>.
970      * @see #getBaseline()
971      * @see Font
972      */

973     public float[] getBaselineOffsets() {
974         float[] offsets = new float[baselineOffsets.length];
975         System.arraycopy(baselineOffsets, 0, offsets, 0, offsets.length);
976         return offsets;
977     }
978
979     /**
980      * Returns the advance of this <code>TextLayout</code>.
981      * The advance is the distance from the origin to the advance of the
982      * rightmost (bottommost) character measuring in the line direction.
983      * @return the advance of this <code>TextLayout</code>.
984      */

985     public float getAdvance() {
986         if (optInfo != null) {
987             try {
988                 return optInfo.getAdvance();
989             }
990             catch (Error JavaDoc e) {
991                 // cache was flushed under optInfo
992
}
993         }
994         ensureCache();
995         return lineMetrics.advance;
996     }
997
998     /**
999      * Returns the advance of this <code>TextLayout</code>, minus trailing
1000     * whitespace.
1001     * @return the advance of this <code>TextLayout</code> without the
1002     * trailing whitespace.
1003     * @see #getAdvance()
1004     */

1005    public float getVisibleAdvance() {
1006        ensureCache();
1007        return visibleAdvance;
1008    }
1009
1010    /**
1011     * Returns the ascent of this <code>TextLayout</code>.
1012     * The ascent is the distance from the top (right) of the
1013     * <code>TextLayout</code> to the baseline. It is always either
1014     * positive or zero. The ascent is sufficient to
1015     * accomodate superscripted text and is the maximum of the sum of the
1016     * ascent, offset, and baseline of each glyph.
1017     * @return the ascent of this <code>TextLayout</code>.
1018     */

1019    public float getAscent() {
1020        if (optInfo != null) {
1021            return optInfo.getCoreMetrics().ascent;
1022        }
1023        ensureCache();
1024        return lineMetrics.ascent;
1025    }
1026
1027    /**
1028     * Returns the descent of this <code>TextLayout</code>.
1029     * The descent is the distance from the baseline to the bottom (left) of
1030     * the <code>TextLayout</code>. It is always either positive or zero.
1031     * The descent is sufficient to accomodate subscripted text and is the
1032     * maximum of the sum of the descent, offset, and baseline of each glyph.
1033     * @return the descent of this <code>TextLayout</code>.
1034     */

1035    public float getDescent() {
1036        if (optInfo != null) {
1037            return optInfo.getCoreMetrics().descent;
1038        }
1039        ensureCache();
1040        return lineMetrics.descent;
1041    }
1042
1043    /**
1044     * Returns the leading of the <code>TextLayout</code>.
1045     * The leading is the suggested interline spacing for this
1046     * <code>TextLayout</code>.
1047     * <p>
1048     * The leading is computed from the leading, descent, and baseline
1049     * of all glyphvectors in the <code>TextLayout</code>. The algorithm
1050     * is roughly as follows:
1051     * <blockquote><pre>
1052     * maxD = 0;
1053     * maxDL = 0;
1054     * for (GlyphVector g in all glyphvectors) {
1055     * maxD = max(maxD, g.getDescent() + offsets[g.getBaseline()]);
1056     * maxDL = max(maxDL, g.getDescent() + g.getLeading() +
1057     * offsets[g.getBaseline()]);
1058     * }
1059     * return maxDL - maxD;
1060     * </pre></blockquote>
1061     * @return the leading of this <code>TextLayout</code>.
1062     */

1063    public float getLeading() {
1064        if (optInfo != null) {
1065            return optInfo.getCoreMetrics().leading;
1066        }
1067        ensureCache();
1068        return lineMetrics.leading;
1069    }
1070
1071    /**
1072     * Returns the bounds of this <code>TextLayout</code>.
1073     * The bounds contains all of the pixels the <code>TextLayout</code>
1074     * can draw. It might not coincide exactly with the ascent, descent,
1075     * origin or advance of the <code>TextLayout</code>.
1076     * @return a {@link Rectangle2D} that is the bounds of this
1077     * <code>TextLayout</code>.
1078     */

1079    public Rectangle2D JavaDoc getBounds() {
1080    if (optInfo != null) {
1081        return optInfo.getVisualBounds();
1082    }
1083
1084        ensureCache();
1085
1086        if (boundsRect == null) {
1087            Rectangle2D JavaDoc lineBounds = textLine.getBounds();
1088            if (dx != 0 || dy != 0) {
1089                lineBounds.setRect(lineBounds.getX() - dx,
1090                                   lineBounds.getY() - dy,
1091                                   lineBounds.getWidth(),
1092                                   lineBounds.getHeight());
1093            }
1094            boundsRect = lineBounds;
1095        }
1096
1097        Rectangle2D JavaDoc bounds = new Rectangle2D.Float JavaDoc();
1098        bounds.setRect(boundsRect);
1099
1100        return bounds;
1101    }
1102
1103    /**
1104     * Returns <code>true</code> if this <code>TextLayout</code> has
1105     * a left-to-right base direction or <code>false</code> if it has
1106     * a right-to-left base direction. The <code>TextLayout</code>
1107     * has a base direction of either left-to-right (LTR) or
1108     * right-to-left (RTL). The base direction is independent of the
1109     * actual direction of text on the line, which may be either LTR,
1110     * RTL, or mixed. Left-to-right layouts by default should position
1111     * flush left. If the layout is on a tabbed line, the
1112     * tabs run left to right, so that logically successive layouts position
1113     * left to right. The opposite is true for RTL layouts. By default they
1114     * should position flush left, and tabs run right-to-left.
1115     * @return <code>true</code> if the base direction of this
1116     * <code>TextLayout</code> is left-to-right; <code>false</code>
1117     * otherwise.
1118     */

1119    public boolean isLeftToRight() {
1120        return (optInfo != null) || textLine.isDirectionLTR();
1121    }
1122
1123    /**
1124     * Returns <code>true</code> if this <code>TextLayout</code> is vertical.
1125     * @return <code>true</code> if this <code>TextLayout</code> is vertical;
1126     * <code>false</code> otherwise.
1127     */

1128    public boolean isVertical() {
1129        return isVerticalLine;
1130    }
1131
1132    /**
1133     * Returns the number of characters represented by this
1134     * <code>TextLayout</code>.
1135     * @return the number of characters in this <code>TextLayout</code>.
1136     */

1137    public int getCharacterCount() {
1138        return characterCount;
1139    }
1140
1141    /*
1142     * carets and hit testing
1143     *
1144     * Positions on a text line are represented by instances of TextHitInfo.
1145     * Any TextHitInfo with characterOffset between 0 and characterCount-1,
1146     * inclusive, represents a valid position on the line. Additionally,
1147     * [-1, trailing] and [characterCount, leading] are valid positions, and
1148     * represent positions at the logical start and end of the line,
1149     * respectively.
1150     *
1151     * The characterOffsets in TextHitInfo's used and returned by TextLayout
1152     * are relative to the beginning of the text layout, not necessarily to
1153     * the beginning of the text storage the client is using.
1154     *
1155     *
1156     * Every valid TextHitInfo has either one or two carets associated with it.
1157     * A caret is a visual location in the TextLayout indicating where text at
1158     * the TextHitInfo will be displayed on screen. If a TextHitInfo
1159     * represents a location on a directional boundary, then there are two
1160     * possible visible positions for newly inserted text. Consider the
1161     * following example, in which capital letters indicate right-to-left text,
1162     * and the overall line direction is left-to-right:
1163     *
1164     * Text Storage: [ a, b, C, D, E, f ]
1165     * Display: a b E D C f
1166     *
1167     * The text hit info (1, t) represents the trailing side of 'b'. If 'q',
1168     * a left-to-right character is inserted into the text storage at this
1169     * location, it will be displayed between the 'b' and the 'E':
1170     *
1171     * Text Storage: [ a, b, q, C, D, E, f ]
1172     * Display: a b q E D C f
1173     *
1174     * However, if a 'W', which is right-to-left, is inserted into the storage
1175     * after 'b', the storage and display will be:
1176     *
1177     * Text Storage: [ a, b, W, C, D, E, f ]
1178     * Display: a b E D C W f
1179     *
1180     * So, for the original text storage, two carets should be displayed for
1181     * location (1, t): one visually between 'b' and 'E' and one visually
1182     * between 'C' and 'f'.
1183     *
1184     *
1185     * When two carets are displayed for a TextHitInfo, one caret is the
1186     * 'strong' caret and the other is the 'weak' caret. The strong caret
1187     * indicates where an inserted character will be displayed when that
1188     * character's direction is the same as the direction of the TextLayout.
1189     * The weak caret shows where an character inserted character will be
1190     * displayed when the character's direction is opposite that of the
1191     * TextLayout.
1192     *
1193     *
1194     * Clients should not be overly concerned with the details of correct
1195     * caret display. TextLayout.getCaretShapes(TextHitInfo) will return an
1196     * array of two paths representing where carets should be displayed.
1197     * The first path in the array is the strong caret; the second element,
1198     * if non-null, is the weak caret. If the second element is null,
1199     * then there is no weak caret for the given TextHitInfo.
1200     *
1201     *
1202     * Since text can be visually reordered, logically consecutive
1203     * TextHitInfo's may not be visually consecutive. One implication of this
1204     * is that a client cannot tell from inspecting a TextHitInfo whether the
1205     * hit represents the first (or last) caret in the layout. Clients
1206     * can call getVisualOtherHit(); if the visual companion is
1207     * (-1, TRAILING) or (characterCount, LEADING), then the hit is at the
1208     * first (last) caret position in the layout.
1209     */

1210
1211    private float[] getCaretInfo(int caret,
1212                                 Rectangle2D JavaDoc bounds,
1213                                 float[] info) {
1214
1215        float top1X, top2X;
1216        float bottom1X, bottom2X;
1217
1218        if (caret == 0 || caret == characterCount) {
1219
1220            float pos;
1221            int logIndex;
1222            if (caret == characterCount) {
1223                logIndex = textLine.visualToLogical(characterCount-1);
1224                pos = textLine.getCharLinePosition(logIndex)
1225                                        + textLine.getCharAdvance(logIndex);
1226            }
1227            else {
1228                logIndex = textLine.visualToLogical(caret);
1229                pos = textLine.getCharLinePosition(logIndex);
1230            }
1231            float angle = textLine.getCharAngle(logIndex);
1232            float shift = textLine.getCharShift(logIndex);
1233            pos += angle * shift;
1234            top1X = top2X = pos + angle*textLine.getCharAscent(logIndex);
1235            bottom1X = bottom2X = pos - angle*textLine.getCharDescent(logIndex);
1236        }
1237        else {
1238
1239            {
1240                int logIndex = textLine.visualToLogical(caret-1);
1241                float angle1 = textLine.getCharAngle(logIndex);
1242                float pos1 = textLine.getCharLinePosition(logIndex)
1243                                    + textLine.getCharAdvance(logIndex);
1244                if (angle1 != 0) {
1245                    pos1 += angle1 * textLine.getCharShift(logIndex);
1246                    top1X = pos1 + angle1*textLine.getCharAscent(logIndex);
1247                    bottom1X = pos1 - angle1*textLine.getCharDescent(logIndex);
1248                }
1249                else {
1250                    top1X = bottom1X = pos1;
1251                }
1252            }
1253            {
1254                int logIndex = textLine.visualToLogical(caret);
1255                float angle2 = textLine.getCharAngle(logIndex);
1256                float pos2 = textLine.getCharLinePosition(logIndex);
1257                if (angle2 != 0) {
1258                    pos2 += angle2*textLine.getCharShift(logIndex);
1259                    top2X = pos2 + angle2*textLine.getCharAscent(logIndex);
1260                    bottom2X = pos2 - angle2*textLine.getCharDescent(logIndex);
1261                }
1262                else {
1263                    top2X = bottom2X = pos2;
1264                }
1265            }
1266        }
1267
1268        float topX = (top1X + top2X) / 2;
1269        float bottomX = (bottom1X + bottom2X) / 2;
1270
1271        if (info == null) {
1272            info = new float[2];
1273        }
1274
1275        if (isVerticalLine) {
1276            info[1] = (float) ((topX - bottomX) / bounds.getWidth());
1277            info[0] = (float) (topX + (info[1]*bounds.getX()));
1278        }
1279        else {
1280            info[1] = (float) ((topX - bottomX) / bounds.getHeight());
1281            info[0] = (float) (bottomX + (info[1]*bounds.getMaxY()));
1282        }
1283
1284        return info;
1285    }
1286
1287    /**
1288     * Returns information about the caret corresponding to <code>hit</code>.
1289     * The first element of the array is the intersection of the caret with
1290     * the baseline. The second element of the array is the inverse slope
1291     * (run/rise) of the caret.
1292     * <p>
1293     * This method is meant for informational use. To display carets, it
1294     * is better to use <code>getCaretShapes</code>.
1295     * @param hit a hit on a character in this <code>TextLayout</code>
1296     * @param bounds the bounds to which the caret info is constructed
1297     * @return a two-element array containing the position and slope of
1298     * the caret.
1299     * @see #getCaretShapes(int, Rectangle2D, TextLayout.CaretPolicy)
1300     * @see Font#getItalicAngle
1301     */

1302    public float[] getCaretInfo(TextHitInfo JavaDoc hit, Rectangle2D JavaDoc bounds) {
1303        ensureCache();
1304        checkTextHit(hit);
1305
1306        return getCaretInfoTestInternal(hit, bounds);
1307    }
1308
1309    // this version provides extra info in the float array
1310
// the first two values are as above
1311
// the next four values are the endpoints of the caret, as computed
1312
// using the hit character's offset (baseline + ssoffset) and
1313
// natural ascent and descent.
1314
// these values are trimmed to the bounds where required to fit,
1315
// but otherwise independent of it.
1316
private float[] getCaretInfoTestInternal(TextHitInfo JavaDoc hit, Rectangle2D JavaDoc bounds) {
1317        ensureCache();
1318        checkTextHit(hit);
1319
1320        float[] info = new float[6];
1321
1322        // get old data first
1323
getCaretInfo(hitToCaret(hit), bounds, info);
1324
1325        // then add our new data
1326
double iangle, ixbase, p1x, p1y, p2x, p2y;
1327
1328        int charix = hit.getCharIndex();
1329        boolean lead = hit.isLeadingEdge();
1330        boolean ltr = textLine.isDirectionLTR();
1331        boolean horiz = !isVertical();
1332
1333        if (charix == -1 || charix == characterCount) {
1334            // !!! note: want non-shifted, baseline ascent and descent here!
1335
// TextLine should return appropriate line metrics object for these values
1336
TextLineMetrics m = textLine.getMetrics();
1337            boolean low = ltr == (charix == -1);
1338            iangle = 0;
1339            if (horiz) {
1340                p1x = p2x = low ? 0 : m.advance;
1341                p1y = -m.ascent;
1342                p2y = m.descent;
1343            } else {
1344                p1y = p2y = low ? 0 : m.advance;
1345                p1x = m.descent;
1346                p2x = m.ascent;
1347            }
1348        } else {
1349            CoreMetrics thiscm = textLine.getCoreMetricsAt(charix);
1350            iangle = thiscm.italicAngle;
1351            ixbase = textLine.getCharLinePosition(charix, lead);
1352        if (thiscm.baselineIndex < 0) {
1353        // this is a graphic, no italics, use entire line height for caret
1354
TextLineMetrics m = textLine.getMetrics();
1355        if (horiz) {
1356            p1x = p2x = ixbase;
1357            if (thiscm.baselineIndex == GraphicAttribute.TOP_ALIGNMENT) {
1358            p1y = -m.ascent;
1359            p2y = p1y + thiscm.height;
1360            } else {
1361            p2y = m.descent;
1362            p1y = p2y - thiscm.height;
1363            }
1364        } else {
1365            p1y = p2y = ixbase;
1366            p1x = m.descent;
1367            p2x = m.ascent;
1368            // !!! top/bottom adjustment not implemented for vertical
1369
}
1370        } else {
1371        float bo = baselineOffsets[thiscm.baselineIndex];
1372        if (horiz) {
1373            ixbase += iangle * thiscm.ssOffset;
1374            p1x = ixbase + iangle * thiscm.ascent;
1375            p2x = ixbase - iangle * thiscm.descent;
1376            p1y = bo - thiscm.ascent;
1377            p2y = bo + thiscm.descent;
1378        } else {
1379            ixbase -= iangle * thiscm.ssOffset;
1380            p1y = ixbase + iangle * thiscm.ascent;
1381            p2y = ixbase - iangle * thiscm.descent;
1382            p1x = bo + thiscm.ascent;
1383            p2x = bo + thiscm.descent;
1384        }
1385        }
1386        }
1387
1388        info[2] = (float)p1x;
1389        info[3] = (float)p1y;
1390        info[4] = (float)p2x;
1391        info[5] = (float)p2y;
1392
1393        return info;
1394    }
1395    
1396    /**
1397     * Returns information about the caret corresponding to <code>hit</code>.
1398     * This method is a convenience overload of <code>getCaretInfo</code> and
1399     * uses the natural bounds of this <code>TextLayout</code>.
1400     * @param hit a hit on a character in this <code>TextLayout</code>
1401     * @return the information about a caret corresponding to a hit.
1402     */

1403    public float[] getCaretInfo(TextHitInfo JavaDoc hit) {
1404
1405        return getCaretInfo(hit, getNaturalBounds());
1406    }
1407
1408    /**
1409     * Returns a caret index corresponding to <code>hit</code>.
1410     * Carets are numbered from left to right (top to bottom) starting from
1411     * zero. This always places carets next to the character hit, on the
1412     * indicated side of the character.
1413     * @param hit a hit on a character in this <code>TextLayout</code>
1414     * @return a caret index corresponding to the specified hit.
1415     */

1416    private int hitToCaret(TextHitInfo JavaDoc hit) {
1417
1418        int hitIndex = hit.getCharIndex();
1419
1420        if (hitIndex < 0) {
1421            return textLine.isDirectionLTR() ? 0 : characterCount;
1422        } else if (hitIndex >= characterCount) {
1423            return textLine.isDirectionLTR() ? characterCount : 0;
1424        }
1425
1426        int visIndex = textLine.logicalToVisual(hitIndex);
1427
1428        if (hit.isLeadingEdge() != textLine.isCharLTR(hitIndex)) {
1429            ++visIndex;
1430        }
1431
1432        return visIndex;
1433    }
1434
1435    /**
1436     * Given a caret index, return a hit whose caret is at the index.
1437     * The hit is NOT guaranteed to be strong!!!
1438     *
1439     * @param caret a caret index.
1440     * @return a hit on this layout whose strong caret is at the requested
1441     * index.
1442     */

1443    private TextHitInfo JavaDoc caretToHit(int caret) {
1444
1445        if (caret == 0 || caret == characterCount) {
1446
1447            if ((caret == characterCount) == textLine.isDirectionLTR()) {
1448                return TextHitInfo.leading(characterCount);
1449            }
1450            else {
1451                return TextHitInfo.trailing(-1);
1452            }
1453        }
1454        else {
1455
1456            int charIndex = textLine.visualToLogical(caret);
1457            boolean leading = textLine.isCharLTR(charIndex);
1458
1459            return leading? TextHitInfo.leading(charIndex)
1460                            : TextHitInfo.trailing(charIndex);
1461        }
1462    }
1463
1464    private boolean caretIsValid(int caret) {
1465
1466        if (caret == characterCount || caret == 0) {
1467            return true;
1468        }
1469
1470        int offset = textLine.visualToLogical(caret);
1471
1472        if (!textLine.isCharLTR(offset)) {
1473            offset = textLine.visualToLogical(caret-1);
1474            if (textLine.isCharLTR(offset)) {
1475                return true;
1476            }
1477        }
1478
1479        // At this point, the leading edge of the character
1480
// at offset is at the given caret.
1481

1482        return textLine.caretAtOffsetIsValid(offset);
1483    }
1484
1485    /**
1486     * Returns the hit for the next caret to the right (bottom); if there
1487     * is no such hit, returns <code>null</code>.
1488     * If the hit character index is out of bounds, an
1489     * {@link IllegalArgumentException} is thrown.
1490     * @param hit a hit on a character in this layout
1491     * @return a hit whose caret appears at the next position to the
1492     * right (bottom) of the caret of the provided hit or <code>null</code>.
1493     */

1494    public TextHitInfo JavaDoc getNextRightHit(TextHitInfo JavaDoc hit) {
1495        ensureCache();
1496        checkTextHit(hit);
1497
1498        int caret = hitToCaret(hit);
1499
1500        if (caret == characterCount) {
1501            return null;
1502        }
1503
1504        do {
1505            ++caret;
1506        } while (!caretIsValid(caret));
1507
1508        return caretToHit(caret);
1509    }
1510
1511    /**
1512     * Returns the hit for the next caret to the right (bottom); if no
1513     * such hit, returns <code>null</code>. The hit is to the right of
1514     * the strong caret at the specified offset, as determined by the
1515     * specified policy.
1516     * The returned hit is the stronger of the two possible
1517     * hits, as determined by the specified policy.
1518     * @param offset an insertion offset in this <code>TextLayout</code>.
1519     * Cannot be less than 0 or greater than this <code>TextLayout</code>
1520     * object's character count.
1521     * @param policy the policy used to select the strong caret
1522     * @return a hit whose caret appears at the next position to the
1523     * right (bottom) of the caret of the provided hit, or <code>null</code>.
1524     */

1525    public TextHitInfo JavaDoc getNextRightHit(int offset, CaretPolicy policy) {
1526
1527        if (offset < 0 || offset > characterCount) {
1528            throw new IllegalArgumentException JavaDoc("Offset out of bounds in TextLayout.getNextRightHit()");
1529        }
1530
1531        if (policy == null) {
1532            throw new IllegalArgumentException JavaDoc("Null CaretPolicy passed to TextLayout.getNextRightHit()");
1533        }
1534
1535        TextHitInfo JavaDoc hit1 = TextHitInfo.afterOffset(offset);
1536        TextHitInfo JavaDoc hit2 = hit1.getOtherHit();
1537
1538        TextHitInfo JavaDoc nextHit = getNextRightHit(policy.getStrongCaret(hit1, hit2, this));
1539
1540        if (nextHit != null) {
1541            TextHitInfo JavaDoc otherHit = getVisualOtherHit(nextHit);
1542            return policy.getStrongCaret(otherHit, nextHit, this);
1543        }
1544        else {
1545            return null;
1546        }
1547    }
1548
1549    /**
1550     * Returns the hit for the next caret to the right (bottom); if no
1551     * such hit, returns <code>null</code>. The hit is to the right of
1552     * the strong caret at the specified offset, as determined by the
1553     * default policy.
1554     * The returned hit is the stronger of the two possible
1555     * hits, as determined by the default policy.
1556     * @param offset an insertion offset in this <code>TextLayout</code>.
1557     * Cannot be less than 0 or greater than the <code>TextLayout</code>
1558     * object's character count.
1559     * @return a hit whose caret appears at the next position to the
1560     * right (bottom) of the caret of the provided hit, or <code>null</code>.
1561     */

1562    public TextHitInfo JavaDoc getNextRightHit(int offset) {
1563
1564        return getNextRightHit(offset, DEFAULT_CARET_POLICY);
1565    }
1566
1567    /**
1568     * Returns the hit for the next caret to the left (top); if no such
1569     * hit, returns <code>null</code>.
1570     * If the hit character index is out of bounds, an
1571     * <code>IllegalArgumentException</code> is thrown.
1572     * @param hit a hit on a character in this <code>TextLayout</code>.
1573     * @return a hit whose caret appears at the next position to the
1574     * left (top) of the caret of the provided hit, or <code>null</code>.
1575     */

1576    public TextHitInfo JavaDoc getNextLeftHit(TextHitInfo JavaDoc hit) {
1577        ensureCache();
1578        checkTextHit(hit);
1579
1580        int caret = hitToCaret(hit);
1581
1582        if (caret == 0) {
1583            return null;
1584        }
1585
1586        do {
1587            --caret;
1588        } while(!caretIsValid(caret));
1589
1590        return caretToHit(caret);
1591    }
1592
1593    /**
1594     * Returns the hit for the next caret to the left (top); if no
1595     * such hit, returns <code>null</code>. The hit is to the left of
1596     * the strong caret at the specified offset, as determined by the
1597     * specified policy.
1598     * The returned hit is the stronger of the two possible
1599     * hits, as determined by the specified policy.
1600     * @param offset an insertion offset in this <code>TextLayout</code>.
1601     * Cannot be less than 0 or greater than this <code>TextLayout</code>
1602     * object's character count.
1603     * @param policy the policy used to select the strong caret
1604     * @return a hit whose caret appears at the next position to the
1605     * left (top) of the caret of the provided hit, or <code>null</code>.
1606     */

1607    public TextHitInfo JavaDoc getNextLeftHit(int offset, CaretPolicy policy) {
1608
1609        if (policy == null) {
1610            throw new IllegalArgumentException JavaDoc("Null CaretPolicy passed to TextLayout.getNextLeftHit()");
1611        }
1612
1613        if (offset < 0 || offset > characterCount) {
1614            throw new IllegalArgumentException JavaDoc("Offset out of bounds in TextLayout.getNextLeftHit()");
1615        }
1616
1617        TextHitInfo JavaDoc hit1 = TextHitInfo.afterOffset(offset);
1618        TextHitInfo JavaDoc hit2 = hit1.getOtherHit();
1619
1620        TextHitInfo JavaDoc nextHit = getNextLeftHit(policy.getStrongCaret(hit1, hit2, this));
1621
1622        if (nextHit != null) {
1623            TextHitInfo JavaDoc otherHit = getVisualOtherHit(nextHit);
1624            return policy.getStrongCaret(otherHit, nextHit, this);
1625        }
1626        else {
1627            return null;
1628        }
1629    }
1630
1631    /**
1632     * Returns the hit for the next caret to the left (top); if no
1633     * such hit, returns <code>null</code>. The hit is to the left of
1634     * the strong caret at the specified offset, as determined by the
1635     * default policy.
1636     * The returned hit is the stronger of the two possible
1637     * hits, as determined by the default policy.
1638     * @param offset an insertion offset in this <code>TextLayout</code>.
1639     * Cannot be less than 0 or greater than this <code>TextLayout</code>
1640     * object's character count.
1641     * @return a hit whose caret appears at the next position to the
1642     * left (top) of the caret of the provided hit, or <code>null</code>.
1643     */

1644    public TextHitInfo JavaDoc getNextLeftHit(int offset) {
1645
1646        return getNextLeftHit(offset, DEFAULT_CARET_POLICY);
1647    }
1648
1649    /**
1650     * Returns the hit on the opposite side of the specified hit's caret.
1651     * @param hit the specified hit
1652     * @return a hit that is on the opposite side of the specified hit's
1653     * caret.
1654     */

1655    public TextHitInfo JavaDoc getVisualOtherHit(TextHitInfo JavaDoc hit) {
1656
1657        ensureCache();
1658        checkTextHit(hit);
1659
1660        int hitCharIndex = hit.getCharIndex();
1661
1662        int charIndex;
1663        boolean leading;
1664
1665        if (hitCharIndex == -1 || hitCharIndex == characterCount) {
1666
1667            int visIndex;
1668            if (textLine.isDirectionLTR() == (hitCharIndex == -1)) {
1669                visIndex = 0;
1670            }
1671            else {
1672                visIndex = characterCount-1;
1673            }
1674
1675            charIndex = textLine.visualToLogical(visIndex);
1676
1677            if (textLine.isDirectionLTR() == (hitCharIndex == -1)) {
1678                // at left end
1679
leading = textLine.isCharLTR(charIndex);
1680            }
1681            else {
1682                // at right end
1683
leading = !textLine.isCharLTR(charIndex);
1684            }
1685        }
1686        else {
1687
1688            int visIndex = textLine.logicalToVisual(hitCharIndex);
1689
1690            boolean movedToRight;
1691            if (textLine.isCharLTR(hitCharIndex) == hit.isLeadingEdge()) {
1692                --visIndex;
1693                movedToRight = false;
1694            }
1695            else {
1696                ++visIndex;
1697                movedToRight = true;
1698            }
1699
1700            if (visIndex > -1 && visIndex < characterCount) {
1701                charIndex = textLine.visualToLogical(visIndex);
1702                leading = movedToRight == textLine.isCharLTR(charIndex);
1703            }
1704            else {
1705                charIndex =
1706                    (movedToRight == textLine.isDirectionLTR())? characterCount : -1;
1707                leading = charIndex == characterCount;
1708            }
1709        }
1710
1711        return leading? TextHitInfo.leading(charIndex) :
1712                                TextHitInfo.trailing(charIndex);
1713    }
1714
1715    private double[] getCaretPath(TextHitInfo JavaDoc hit, Rectangle2D JavaDoc bounds) {
1716        float[] info = getCaretInfo(hit, bounds);
1717        return new double[] { info[2], info[3], info[4], info[5] };
1718    }
1719
1720    /**
1721     * Return an array of four floats corresponding the endpoints of the caret
1722     * x0, y0, x1, y1.
1723     *
1724     * This creates a line along the slope of the caret intersecting the
1725     * baseline at the caret
1726     * position, and extending from ascent above the baseline to descent below
1727     * it.
1728     */

1729    private double[] getCaretPath(int caret, Rectangle2D JavaDoc bounds,
1730                                  boolean clipToBounds) {
1731
1732        float[] info = getCaretInfo(caret, bounds, null);
1733
1734        double pos = info[0];
1735        double slope = info[1];
1736
1737        double x0, y0, x1, y1;
1738        double x2 = -3141.59, y2 = -2.7; // values are there to make compiler happy
1739

1740        double left = bounds.getX();
1741        double right = left + bounds.getWidth();
1742        double top = bounds.getY();
1743        double bottom = top + bounds.getHeight();
1744
1745        boolean threePoints = false;
1746
1747        if (isVerticalLine) {
1748
1749            if (slope >= 0) {
1750                x0 = left;
1751                x1 = right;
1752            }
1753            else {
1754                x1 = left;
1755                x0 = right;
1756            }
1757
1758            y0 = pos + x0 * slope;
1759            y1 = pos + x1 * slope;
1760
1761            // y0 <= y1, always
1762

1763            if (clipToBounds) {
1764                if (y0 < top) {
1765                    if (slope <= 0 || y1 <= top) {
1766                        y0 = y1 = top;
1767                    }
1768                    else {
1769                        threePoints = true;
1770                        y0 = top;
1771                        y2 = top;
1772                        x2 = x1 + (top-y1)/slope;
1773                        if (y1 > bottom) {
1774                            y1 = bottom;
1775                        }
1776                    }
1777                }
1778                else if (y1 > bottom) {
1779                    if (slope >= 0 || y0 >= bottom) {
1780                        y0 = y1 = bottom;
1781                    }
1782                    else {
1783                        threePoints = true;
1784                        y1 = bottom;
1785                        y2 = bottom;
1786                        x2 = x0 + (bottom-x1)/slope;
1787                    }
1788                }
1789            }
1790
1791        }
1792        else {
1793
1794            if (slope >= 0) {
1795                y0 = bottom;
1796                y1 = top;
1797            }
1798            else {
1799                y1 = bottom;
1800                y0 = top;
1801            }
1802
1803            x0 = pos - y0 * slope;
1804            x1 = pos - y1 * slope;
1805            
1806            // x0 <= x1, always
1807

1808            if (clipToBounds) {
1809                if (x0 < left) {
1810                    if (slope <= 0 || x1 <= left) {
1811                        x0 = x1 = left;
1812                    }
1813                    else {
1814                        threePoints = true;
1815                        x0 = left;
1816                        x2 = left;
1817                        y2 = y1 - (left-x1)/slope;
1818                        if (x1 > right) {
1819                            x1 = right;
1820                        }
1821                    }
1822                }
1823                else if (x1 > right) {
1824                    if (slope >= 0 || x0 >= right) {
1825                        x0 = x1 = right;
1826                    }
1827                    else {
1828                        threePoints = true;
1829                        x1 = right;
1830                        x2 = right;
1831                        y2 = y0 - (right-x0)/slope;
1832                    }
1833                }
1834            }
1835        }
1836
1837        return threePoints?
1838                    new double[] { x0, y0, x2, y2, x1, y1 } :
1839                    new double[] { x0, y0, x1, y1 };
1840    }
1841
1842
1843    private static GeneralPath JavaDoc pathToShape(double[] path, boolean close) {
1844        GeneralPath JavaDoc result = new GeneralPath JavaDoc(GeneralPath.WIND_EVEN_ODD, path.length);
1845        result.moveTo((float)path[0], (float)path[1]);
1846        for (int i = 2; i < path.length; i += 2) {
1847            result.lineTo((float)path[i], (float)path[i+1]);
1848        }
1849        if (close) {
1850            result.closePath();
1851        }
1852
1853        return result;
1854    }
1855
1856    /**
1857     * Returns a {@link Shape} representing the caret at the specified
1858     * hit inside the specified bounds.
1859     * @param hit the hit at which to generate the caret
1860     * @param bounds the bounds of the <code>TextLayout</code> to use
1861     * in generating the caret.
1862     * @return a <code>Shape</code> representing the caret.
1863     */

1864    public Shape JavaDoc getCaretShape(TextHitInfo JavaDoc hit, Rectangle2D JavaDoc bounds) {
1865    ensureCache();
1866        checkTextHit(hit);
1867
1868        if (bounds == null) {
1869            throw new IllegalArgumentException JavaDoc("Null Rectangle2D passed to TextLayout.getCaret()");
1870        }
1871
1872// int hitCaret = hitToCaret(hit);
1873
// GeneralPath hitShape =
1874
// pathToShape(getCaretPath(hitCaret, bounds, false), false);
1875

1876        return pathToShape(getCaretPath(hit, bounds), false);
1877
1878        //return new Highlight(hitShape, true);
1879
// return hitShape;
1880
}
1881
1882    /**
1883     * Returns a <code>Shape</code> representing the caret at the specified
1884     * hit inside the natural bounds of this <code>TextLayout</code>.
1885     * @param hit the hit at which to generate the caret
1886     * @return a <code>Shape</code> representing the caret.
1887     */

1888    public Shape JavaDoc getCaretShape(TextHitInfo JavaDoc hit) {
1889
1890        return getCaretShape(hit, getNaturalBounds());
1891    }
1892
1893    /**
1894     * Return the "stronger" of the TextHitInfos. The TextHitInfos
1895     * should be logical or visual counterparts. They are not
1896     * checked for validity.
1897     */

1898    private final TextHitInfo JavaDoc getStrongHit(TextHitInfo JavaDoc hit1, TextHitInfo JavaDoc hit2) {
1899
1900        // right now we're using the following rule for strong hits:
1901
// A hit on a character with a lower level
1902
// is stronger than one on a character with a higher level.
1903
// If this rule ties, the hit on the leading edge of a character wins.
1904
// If THIS rule ties, hit1 wins. Both rules shouldn't tie, unless the
1905
// infos aren't counterparts of some sort.
1906

1907        byte hit1Level = getCharacterLevel(hit1.getCharIndex());
1908        byte hit2Level = getCharacterLevel(hit2.getCharIndex());
1909
1910        if (hit1Level == hit2Level) {
1911            if (hit2.isLeadingEdge() && !hit1.isLeadingEdge()) {
1912                return hit2;
1913            }
1914            else {
1915                return hit1;
1916            }
1917        }
1918        else {
1919            return (hit1Level < hit2Level)? hit1 : hit2;
1920        }
1921    }
1922
1923    /**
1924     * Returns the level of the character at <code>index</code>.
1925     * Indices -1 and <code>characterCount</code> are assigned the base
1926     * level of this <code>TextLayout</code>.
1927     * @param index the index of the character from which to get the level
1928     * @return the level of the character at the specified index.
1929     */

1930    public byte getCharacterLevel(int index) {
1931
1932        // hmm, allow indices at endpoints? For now, yes.
1933
if (index < -1 || index > characterCount) {
1934            throw new IllegalArgumentException JavaDoc("Index is out of range in getCharacterLevel.");
1935        }
1936
1937        if (optInfo != null) {
1938            return 0;
1939        }
1940
1941    ensureCache();
1942        if (index == -1 || index == characterCount) {
1943             return (byte) (textLine.isDirectionLTR()? 0 : 1);
1944        }
1945
1946        return textLine.getCharLevel(index);
1947    }
1948
1949    /**
1950     * Returns two paths corresponding to the strong and weak caret.
1951     * @param offset an offset in this <code>TextLayout</code>
1952     * @param bounds the bounds to which to extend the carets
1953     * @param policy the specified <code>CaretPolicy</code>
1954     * @return an array of two paths. Element zero is the strong
1955     * caret. If there are two carets, element one is the weak caret,
1956     * otherwise it is <code>null</code>.
1957     */

1958    public Shape JavaDoc[] getCaretShapes(int offset, Rectangle2D JavaDoc bounds, CaretPolicy policy) {
1959
1960        ensureCache();
1961
1962        if (offset < 0 || offset > characterCount) {
1963            throw new IllegalArgumentException JavaDoc("Offset out of bounds in TextLayout.getCaretShapes()");
1964        }
1965
1966        if (bounds == null) {
1967            throw new IllegalArgumentException JavaDoc("Null Rectangle2D passed to TextLayout.getCaretShapes()");
1968        }
1969
1970        if (policy == null) {
1971            throw new IllegalArgumentException JavaDoc("Null CaretPolicy passed to TextLayout.getCaretShapes()");
1972        }
1973
1974        Shape JavaDoc[] result = new Shape JavaDoc[2];
1975
1976        TextHitInfo JavaDoc hit = TextHitInfo.afterOffset(offset);
1977
1978        int hitCaret = hitToCaret(hit);
1979// Shape hitShape =
1980
// pathToShape(getCaretPath(hitCaret, bounds, false), false);
1981

1982        Shape JavaDoc hitShape = pathToShape(getCaretPath(hit, bounds), false);
1983        TextHitInfo JavaDoc otherHit = hit.getOtherHit();
1984        int otherCaret = hitToCaret(otherHit);
1985
1986        if (hitCaret == otherCaret) {
1987            result[0] = hitShape;
1988        }
1989        else { // more than one caret
1990
// Shape otherShape =
1991
// pathToShape(getCaretPath(otherCaret, bounds, false), false);
1992

1993            Shape JavaDoc otherShape = pathToShape(getCaretPath(otherHit, bounds), false);
1994
1995            TextHitInfo JavaDoc strongHit = policy.getStrongCaret(hit, otherHit, this);
1996            boolean hitIsStrong = strongHit.equals(hit);
1997
1998            if (hitIsStrong) {// then other is weak
1999
result[0] = hitShape;
2000                result[1] = otherShape;
2001            }
2002            else {
2003                result[0] = otherShape;
2004                result[1] = hitShape;
2005            }
2006        }
2007
2008        return result;
2009    }
2010
2011    /**
2012     * Returns two paths corresponding to the strong and weak caret.
2013     * This method is a convenience overload of <code>getCaretShapes</code>
2014     * that uses the default caret policy.
2015     * @param offset an offset in this <code>TextLayout</code>
2016     * @param bounds the bounds to which to extend the carets
2017     * @return two paths corresponding to the strong and weak caret as
2018     * defined by the <code>DEFAULT_CARET_POLICY</code>
2019     */

2020    public Shape JavaDoc[] getCaretShapes(int offset, Rectangle2D JavaDoc bounds) {
2021        // {sfb} parameter checking is done in overloaded version
2022
return getCaretShapes(offset, bounds, DEFAULT_CARET_POLICY);
2023    }
2024
2025    /**
2026     * Returns two paths corresponding to the strong and weak caret.
2027     * This method is a convenience overload of <code>getCaretShapes</code>
2028     * that uses the default caret policy and this <code>TextLayout</code>
2029     * object's natural bounds.
2030     * @param offset an offset in this <code>TextLayout</code>
2031     * @return two paths corresponding to the strong and weak caret as
2032     * defined by the <code>DEFAULT_CARET_POLICY</code>
2033     */

2034    public Shape JavaDoc[] getCaretShapes(int offset) {
2035        // {sfb} parameter checking is done in overloaded version
2036
return getCaretShapes(offset, getNaturalBounds(), DEFAULT_CARET_POLICY);
2037    }
2038
2039    // A utility to return a path enclosing the given path
2040
// Path0 must be left or top of path1
2041
// {jbr} no assumptions about size of path0, path1 anymore.
2042
private GeneralPath JavaDoc boundingShape(double[] path0, double[] path1) {
2043
2044        // Really, we want the path to be a convex hull around all of the
2045
// points in path0 and path1. But we can get by with less than
2046
// that. We do need to prevent the two segments which
2047
// join path0 to path1 from crossing each other. So, if we
2048
// traverse path0 from top to bottom, we'll traverse path1 from
2049
// bottom to top (and vice versa).
2050

2051        GeneralPath JavaDoc result = pathToShape(path0, false);
2052
2053        boolean sameDirection;
2054                
2055        if (isVerticalLine) {
2056            sameDirection = (path0[1] > path0[path0.length-1]) ==
2057                            (path1[1] > path1[path1.length-1]);
2058        }
2059        else {
2060            sameDirection = (path0[0] > path0[path0.length-2]) ==
2061                            (path1[0] > path1[path1.length-2]);
2062        }
2063
2064        int start;
2065        int limit;
2066        int increment;
2067
2068        if (sameDirection) {
2069            start = path1.length-2;
2070            limit = -2;
2071            increment = -2;
2072        }
2073        else {
2074            start = 0;
2075            limit = path1.length;
2076            increment = 2;
2077        }
2078
2079        for (int i = start; i != limit; i += increment) {
2080            result.lineTo((float)path1[i], (float)path1[i+1]);
2081        }
2082
2083        result.closePath();
2084
2085        return result;
2086    }
2087
2088    // A utility to convert a pair of carets into a bounding path
2089
// {jbr} Shape is never outside of bounds.
2090
private GeneralPath JavaDoc caretBoundingShape(int caret0,
2091                                           int caret1,
2092                                           Rectangle2D JavaDoc bounds) {
2093
2094        if (caret0 > caret1) {
2095            int temp = caret0;
2096            caret0 = caret1;
2097            caret1 = temp;
2098        }
2099
2100        return boundingShape(getCaretPath(caret0, bounds, true),
2101                             getCaretPath(caret1, bounds, true));
2102    }
2103
2104    /*
2105     * A utility to return the path bounding the area to the left (top) of the
2106     * layout.
2107     * Shape is never outside of bounds.
2108     */

2109    private GeneralPath JavaDoc leftShape(Rectangle2D JavaDoc bounds) {
2110
2111        double[] path0;
2112        if (isVerticalLine) {
2113            path0 = new double[] { bounds.getX(), bounds.getY(),
2114                                       bounds.getX() + bounds.getWidth(),
2115                                       bounds.getY() };
2116        } else {
2117            path0 = new double[] { bounds.getX(),
2118                                       bounds.getY() + bounds.getHeight(),
2119                                       bounds.getX(), bounds.getY() };
2120        }
2121
2122        double[] path1 = getCaretPath(0, bounds, true);
2123
2124        return boundingShape(path0, path1);
2125    }
2126
2127    /*
2128     * A utility to return the path bounding the area to the right (bottom) of
2129     * the layout.
2130     */

2131    private GeneralPath JavaDoc rightShape(Rectangle2D JavaDoc bounds) {
2132        double[] path1;
2133        if (isVerticalLine) {
2134            path1 = new double[] {
2135                bounds.getX(),
2136                bounds.getY() + bounds.getHeight(),
2137                bounds.getX() + bounds.getWidth(),
2138                bounds.getY() + bounds.getHeight()
2139            };
2140        } else {
2141            path1 = new double[] {
2142                bounds.getX() + bounds.getWidth(),
2143                bounds.getY() + bounds.getHeight(),
2144                bounds.getX() + bounds.getWidth(),
2145                bounds.getY()
2146            };
2147        }
2148
2149        double[] path0 = getCaretPath(characterCount, bounds, true);
2150
2151        return boundingShape(path0, path1);
2152    }
2153
2154    /**
2155     * Returns the logical ranges of text corresponding to a visual selection.
2156     * @param firstEndpoint an endpoint of the visual range
2157     * @param secondEndpoint the other endpoint of the visual range.
2158     * This endpoint can be less than <code>firstEndpoint</code>.
2159     * @return an array of integers representing start/limit pairs for the
2160     * selected ranges.
2161     * @see #getVisualHighlightShape(TextHitInfo, TextHitInfo, Rectangle2D)
2162     */

2163    public int[] getLogicalRangesForVisualSelection(TextHitInfo JavaDoc firstEndpoint,
2164                                                    TextHitInfo JavaDoc secondEndpoint) {
2165        ensureCache();
2166
2167        checkTextHit(firstEndpoint);
2168        checkTextHit(secondEndpoint);
2169
2170        // !!! probably want to optimize for all LTR text
2171

2172        boolean[] included = new boolean[characterCount];
2173
2174        int startIndex = hitToCaret(firstEndpoint);
2175        int limitIndex = hitToCaret(secondEndpoint);
2176
2177        if (startIndex > limitIndex) {
2178            int t = startIndex;
2179            startIndex = limitIndex;
2180            limitIndex = t;
2181        }
2182
2183        /*
2184         * now we have the visual indexes of the glyphs at the start and limit
2185         * of the selection range walk through runs marking characters that
2186         * were included in the visual range there is probably a more efficient
2187         * way to do this, but this ought to work, so hey
2188         */

2189
2190        if (startIndex < limitIndex) {
2191            int visIndex = startIndex;
2192            while (visIndex < limitIndex) {
2193                included[textLine.visualToLogical(visIndex)] = true;
2194                ++visIndex;
2195            }
2196        }
2197
2198        /*
2199         * count how many runs we have, ought to be one or two, but perhaps
2200         * things are especially weird
2201         */

2202        int count = 0;
2203        boolean inrun = false;
2204        for (int i = 0; i < characterCount; i++) {
2205            if (included[i] != inrun) {
2206                inrun = !inrun;
2207                if (inrun) {
2208                    count++;
2209                }
2210            }
2211        }
2212
2213        int[] ranges = new int[count * 2];
2214        count = 0;
2215        inrun = false;
2216        for (int i = 0; i < characterCount; i++) {
2217            if (included[i] != inrun) {
2218                ranges[count++] = i;
2219                inrun = !inrun;
2220            }
2221        }
2222        if (inrun) {
2223            ranges[count++] = characterCount;
2224        }
2225
2226        return ranges;
2227    }
2228
2229    /**
2230     * Returns a path enclosing the visual selection in the specified range,
2231     * extended to <code>bounds</code>.
2232     * <p>
2233     * If the selection includes the leftmost (topmost) position, the selection
2234     * is extended to the left (top) of <code>bounds</code>. If the
2235     * selection includes the rightmost (bottommost) position, the selection
2236     * is extended to the right (bottom) of the bounds. The height
2237     * (width on vertical lines) of the selection is always extended to
2238     * <code>bounds</code>.
2239     * <p>
2240     * Although the selection is always contiguous, the logically selected
2241     * text can be discontiguous on lines with mixed-direction text. The
2242     * logical ranges of text selected can be retrieved using
2243     * <code>getLogicalRangesForVisualSelection</code>. For example,
2244     * consider the text 'ABCdef' where capital letters indicate
2245     * right-to-left text, rendered on a right-to-left line, with a visual
2246     * selection from 0L (the leading edge of 'A') to 3T (the trailing edge
2247     * of 'd'). The text appears as follows, with bold underlined areas
2248     * representing the selection:
2249     * <br><pre>
2250     * d<u><b>efCBA </b></u>
2251     * </pre>
2252     * The logical selection ranges are 0-3, 4-6 (ABC, ef) because the
2253     * visually contiguous text is logically discontiguous. Also note that
2254     * since the rightmost position on the layout (to the right of 'A') is
2255     * selected, the selection is extended to the right of the bounds.
2256     * @param firstEndpoint one end of the visual selection
2257     * @param secondEndpoint the other end of the visual selection
2258     * @param bounds the bounding rectangle to which to extend the selection
2259     * @return a <code>Shape</code> enclosing the selection.
2260     * @see #getLogicalRangesForVisualSelection(TextHitInfo, TextHitInfo)
2261     * @see #getLogicalHighlightShape(int, int, Rectangle2D)
2262     */

2263    public Shape JavaDoc getVisualHighlightShape(TextHitInfo JavaDoc firstEndpoint,
2264                                        TextHitInfo JavaDoc secondEndpoint,
2265                                        Rectangle2D JavaDoc bounds)
2266    {
2267        ensureCache();
2268
2269        checkTextHit(firstEndpoint);
2270        checkTextHit(secondEndpoint);
2271
2272        if(bounds == null) {
2273                throw new IllegalArgumentException JavaDoc("Null Rectangle2D passed to TextLayout.getVisualHighlightShape()");
2274        }
2275
2276        GeneralPath JavaDoc result = new GeneralPath JavaDoc(GeneralPath.WIND_EVEN_ODD);
2277
2278        int firstCaret = hitToCaret(firstEndpoint);
2279        int secondCaret = hitToCaret(secondEndpoint);
2280
2281        result.append(caretBoundingShape(firstCaret, secondCaret, bounds),
2282                      false);
2283
2284        if (firstCaret == 0 || secondCaret == 0) {
2285            result.append(leftShape(bounds), false);
2286        }
2287
2288        if (firstCaret == characterCount || secondCaret == characterCount) {
2289            result.append(rightShape(bounds), false);
2290        }
2291
2292        //return new Highlight(result, false);
2293
return result;
2294    }
2295
2296    /**
2297     * Returns a <code>Shape</code> enclosing the visual selection in the
2298     * specified range, extended to the bounds. This method is a
2299     * convenience overload of <code>getVisualHighlightShape</code> that
2300     * uses the natural bounds of this <code>TextLayout</code>.
2301     * @param firstEndpoint one end of the visual selection
2302     * @param secondEndpoint the other end of the visual selection
2303     * @return a <code>Shape</code> enclosing the selection.
2304     */

2305    public Shape JavaDoc getVisualHighlightShape(TextHitInfo JavaDoc firstEndpoint,
2306                                             TextHitInfo JavaDoc secondEndpoint) {
2307        return getVisualHighlightShape(firstEndpoint, secondEndpoint, getNaturalBounds());
2308    }
2309
2310    /**
2311     * Returns a <code>Shape</code> enclosing the logical selection in the
2312     * specified range, extended to the specified <code>bounds</code>.
2313     * <p>
2314     * If the selection range includes the first logical character, the
2315     * selection is extended to the portion of <code>bounds</code> before
2316     * the start of this <code>TextLayout</code>. If the range includes
2317     * the last logical character, the selection is extended to the portion
2318     * of <code>bounds</code> after the end of this <code>TextLayout</code>.
2319     * The height (width on vertical lines) of the selection is always
2320     * extended to <code>bounds</code>.
2321     * <p>
2322     * The selection can be discontiguous on lines with mixed-direction text.
2323     * Only those characters in the logical range between start and limit
2324     * appear selected. For example, consider the text 'ABCdef' where capital
2325     * letters indicate right-to-left text, rendered on a right-to-left line,
2326     * with a logical selection from 0 to 4 ('ABCd'). The text appears as
2327     * follows, with bold standing in for the selection, and underlining for
2328     * the extension:
2329     * <br><pre>
2330     * <u><b>d</b></u>ef<u><b>CBA </b></u>
2331     * </pre>
2332     * The selection is discontiguous because the selected characters are
2333     * visually discontiguous. Also note that since the range includes the
2334     * first logical character (A), the selection is extended to the portion
2335     * of the <code>bounds</code> before the start of the layout, which in
2336     * this case (a right-to-left line) is the right portion of the
2337     * <code>bounds</code>.
2338     * @param firstEndpoint an endpoint in the range of characters to select
2339     * @param secondEndpoint the other endpoint of the range of characters
2340     * to select. Can be less than <code>firstEndpoint</code>. The range
2341     * includes the character at min(firstEndpoint, secondEndpoint), but
2342     * excludes max(firstEndpoint, secondEndpoint).
2343     * @param bounds the bounding rectangle to which to extend the selection
2344     * @return an area enclosing the selection.
2345     * @see #getVisualHighlightShape(TextHitInfo, TextHitInfo, Rectangle2D)
2346     */

2347    public Shape JavaDoc getLogicalHighlightShape(int firstEndpoint,
2348                                         int secondEndpoint,
2349                                         Rectangle2D JavaDoc bounds) {
2350        if (bounds == null) {
2351            throw new IllegalArgumentException JavaDoc("Null Rectangle2D passed to TextLayout.getLogicalHighlightShape()");
2352        }
2353
2354        ensureCache();
2355
2356        if (firstEndpoint > secondEndpoint) {
2357            int t = firstEndpoint;
2358            firstEndpoint = secondEndpoint;
2359            secondEndpoint = t;
2360        }
2361
2362        if(firstEndpoint < 0 || secondEndpoint > characterCount) {
2363            throw new IllegalArgumentException JavaDoc("Range is invalid in TextLayout.getLogicalHighlightShape()");
2364        }
2365
2366        GeneralPath JavaDoc result = new GeneralPath JavaDoc(GeneralPath.WIND_EVEN_ODD);
2367
2368        int[] carets = new int[10]; // would this ever not handle all cases?
2369
int count = 0;
2370
2371        if (firstEndpoint < secondEndpoint) {
2372            int logIndex = firstEndpoint;
2373            do {
2374                carets[count++] = hitToCaret(TextHitInfo.leading(logIndex));
2375                boolean ltr = textLine.isCharLTR(logIndex);
2376
2377                do {
2378                    logIndex++;
2379                } while (logIndex < secondEndpoint && textLine.isCharLTR(logIndex) == ltr);
2380
2381                int hitCh = logIndex;
2382                carets[count++] = hitToCaret(TextHitInfo.trailing(hitCh - 1));
2383
2384                if (count == carets.length) {
2385                    int[] temp = new int[carets.length + 10];
2386                    System.arraycopy(carets, 0, temp, 0, count);
2387                    carets = temp;
2388                }
2389            } while (logIndex < secondEndpoint);
2390        }
2391        else {
2392            count = 2;
2393            carets[0] = carets[1] = hitToCaret(TextHitInfo.leading(firstEndpoint));
2394        }
2395
2396        // now create paths for pairs of carets
2397

2398        for (int i = 0; i < count; i += 2) {
2399            result.append(caretBoundingShape(carets[i], carets[i+1], bounds),
2400                          false);
2401        }
2402
2403        if (firstEndpoint != secondEndpoint) {
2404            if ((textLine.isDirectionLTR() && firstEndpoint == 0) || (!textLine.isDirectionLTR() &&
2405                                                                      secondEndpoint == characterCount)) {
2406                result.append(leftShape(bounds), false);
2407            }
2408
2409            if ((textLine.isDirectionLTR() && secondEndpoint == characterCount) ||
2410                (!textLine.isDirectionLTR() && firstEndpoint == 0)) {
2411                result.append(rightShape(bounds), false);
2412            }
2413        }
2414
2415        return result;
2416    }
2417
2418    /**
2419     * Returns a <code>Shape</code> enclosing the logical selection in the
2420     * specified range, extended to the natural bounds of this
2421     * <code>TextLayout</code>. This method is a convenience overload of
2422     * <code>getLogicalHighlightShape</code> that uses the natural bounds of
2423     * this <code>TextLayout</code>.
2424     * @param firstEndpoint an endpoint in the range of characters to select
2425     * @param secondEndpoint the other endpoint of the range of characters
2426     * to select. Can be less than <code>firstEndpoint</code>. The range
2427     * includes the character at min(firstEndpoint, secondEndpoint), but
2428     * excludes max(firstEndpoint, secondEndpoint).
2429     * @return a <code>Shape</code> enclosing the selection.
2430     */

2431    public Shape JavaDoc getLogicalHighlightShape(int firstEndpoint, int secondEndpoint) {
2432
2433        return getLogicalHighlightShape(firstEndpoint, secondEndpoint, getNaturalBounds());
2434    }
2435
2436    /**
2437     * Returns the black box bounds of the characters in the specified range.
2438     * The black box bounds is an area consisting of the union of the bounding
2439     * boxes of all the glyphs corresponding to the characters between start
2440     * and limit. This path may be disjoint.
2441     * @param firstEndpoint one end of the character range
2442     * @param secondEndpoint the other end of the character range. Can be
2443     * less than <code>firstEndpoint</code>.
2444     * @return a <code>path</code> enclosing the black box bounds.
2445     */

2446    public Shape JavaDoc getBlackBoxBounds(int firstEndpoint, int secondEndpoint) {
2447        ensureCache();
2448
2449        if (firstEndpoint > secondEndpoint) {
2450            int t = firstEndpoint;
2451            firstEndpoint = secondEndpoint;
2452            secondEndpoint = t;
2453        }
2454
2455        if (firstEndpoint < 0 || secondEndpoint > characterCount) {
2456            throw new IllegalArgumentException JavaDoc("Invalid range passed to TextLayout.getBlackBoxBounds()");
2457        }
2458
2459        /*
2460         * return an area that consists of the bounding boxes of all the
2461         * characters from firstEndpoint to limit
2462         */

2463
2464        GeneralPath JavaDoc result = new GeneralPath JavaDoc(GeneralPath.WIND_NON_ZERO);
2465
2466        if (firstEndpoint < characterCount) {
2467            for (int logIndex = firstEndpoint;
2468                        logIndex < secondEndpoint;
2469                        logIndex++) {
2470
2471        Rectangle2D JavaDoc r = textLine.getCharBounds(logIndex);
2472        if (!r.isEmpty()) {
2473            result.append(r, false);
2474                }
2475            }
2476        }
2477
2478        if (dx != 0 || dy != 0) {
2479            AffineTransform JavaDoc translate = new AffineTransform JavaDoc();
2480            translate.setToTranslation(dx, dy);
2481            result = (GeneralPath JavaDoc) result.createTransformedShape(translate);
2482        }
2483
2484        //return new Highlight(result, false);
2485
return result;
2486    }
2487
2488    /**
2489     * Returns the distance from the point (x,&nbsp;y) to the caret along
2490     * the line direction defined in <code>caretInfo</code>. Distance is
2491     * negative if the point is to the left of the caret on a horizontal
2492     * line, or above the caret on a vertical line.
2493     * Utility for use by hitTestChar.
2494     */

2495    private float caretToPointDistance(float[] caretInfo, float x, float y) {
2496        // distanceOffBaseline is negative if you're 'above' baseline
2497

2498        float lineDistance = isVerticalLine? y : x;
2499        float distanceOffBaseline = isVerticalLine? -x : y;
2500
2501        return lineDistance - caretInfo[0] +
2502            (distanceOffBaseline*caretInfo[1]);
2503    }
2504
2505    /**
2506     * Returns a <code>TextHitInfo</code> corresponding to the
2507     * specified point.
2508     * Coordinates outside the bounds of the <code>TextLayout</code>
2509     * map to hits on the leading edge of the first logical character,
2510     * or the trailing edge of the last logical character, as appropriate,
2511     * regardless of the position of that character in the line. Only the
2512     * direction along the baseline is used to make this evaluation.
2513     * @param x the x offset from the origin of this
2514     * <code>TextLayout</code>
2515     * @param y the y offset from the origin of this
2516     * <code>TextLayout</code>
2517     * @param bounds the bounds of the <code>TextLayout</code>
2518     * @return a hit describing the character and edge (leading or trailing)
2519     * under the specified point.
2520     */

2521    public TextHitInfo JavaDoc hitTestChar(float x, float y, Rectangle2D JavaDoc bounds) {
2522        // check boundary conditions
2523

2524        if (isVertical()) {
2525            if (y < bounds.getMinY()) {
2526                return TextHitInfo.leading(0);
2527            } else if (y >= bounds.getMaxY()) {
2528                return TextHitInfo.trailing(characterCount-1);
2529            }
2530        } else {
2531            if (x < bounds.getMinX()) {
2532                return isLeftToRight() ? TextHitInfo.leading(0) : TextHitInfo.trailing(characterCount-1);
2533            } else if (x >= bounds.getMaxX()) {
2534                return isLeftToRight() ? TextHitInfo.trailing(characterCount-1) : TextHitInfo.leading(0);
2535            }
2536        }
2537 
2538        // revised hit test
2539
// the original seems too complex and fails miserably with italic offsets
2540
// the natural tendency is to move towards the character you want to hit
2541
// so we'll just measure distance to the center of each character's visual
2542
// bounds, pick the closest one, then see which side of the character's
2543
// center line (italic) the point is on.
2544
// this tends to make it easier to hit narrow characters, which can be a
2545
// bit odd if you're visually over an adjacent wide character. this makes
2546
// a difference with bidi, so perhaps i need to revisit this yet again.
2547

2548        double distance = Double.MAX_VALUE;
2549        int index = 0;
2550        int trail = -1;
2551        CoreMetrics lcm = null;
2552        float icx = 0, icy = 0, ia = 0, cy = 0, dya = 0, ydsq = 0;
2553
2554        for (int i = 0; i < characterCount; ++i) {
2555            if (!textLine.caretAtOffsetIsValid(i)) {
2556                continue;
2557            }
2558            if (trail == -1) {
2559                trail = i;
2560            }
2561            CoreMetrics cm = textLine.getCoreMetricsAt(i);
2562            if (cm != lcm) {
2563                lcm = cm;
2564                // just work around baseline mess for now
2565
if (cm.baselineIndex == GraphicAttribute.TOP_ALIGNMENT) {
2566                    cy = -(textLine.getMetrics().ascent - cm.ascent) + cm.ssOffset;
2567                } else if (cm.baselineIndex == GraphicAttribute.BOTTOM_ALIGNMENT) {
2568                    cy = textLine.getMetrics().descent - cm.descent + cm.ssOffset;
2569                } else {
2570                    cy = cm.effectiveBaselineOffset(baselineOffsets) + cm.ssOffset;
2571                }
2572                float dy = (cm.descent - cm.ascent) / 2 - cy;
2573                dya = dy * cm.italicAngle;
2574                cy += dy;
2575                ydsq = (cy - y)*(cy - y);
2576            }
2577            float cx = textLine.getCharXPosition(i);
2578            float ca = textLine.getCharAdvance(i);
2579            float dx = ca / 2;
2580            cx += dx - dya;
2581 
2582            // proximity in x (along baseline) is two times as important as proximity in y
2583
double nd = Math.sqrt(4*(cx - x)*(cx - x) + ydsq);
2584            if (nd < distance) {
2585                distance = nd;
2586                index = i;
2587                trail = -1;
2588                icx = cx; icy = cy; ia = cm.italicAngle;
2589            }
2590        }
2591        boolean left = x < icx - (y - icy) * ia;
2592        boolean leading = textLine.isCharLTR(index) == left;
2593        if (trail == -1) {
2594            trail = characterCount;
2595        }
2596        TextHitInfo JavaDoc result = leading ? TextHitInfo.leading(index) :
2597            TextHitInfo.trailing(trail-1);
2598        return result;
2599    }
2600
2601    /**
2602     * Returns a <code>TextHitInfo</code> corresponding to the
2603     * specified point. This method is a convenience overload of
2604     * <code>hitTestChar</code> that uses the natural bounds of this
2605     * <code>TextLayout</code>.
2606     * @param x the x offset from the origin of this <code>TextLayout</code>
2607     * @param y the y offset from the origin of this
2608     * <code>TextLayout</code>
2609     * @return a hit describing the character and edge (leading or trailing)
2610     * under the specified point.
2611     */

2612    public TextHitInfo JavaDoc hitTestChar(float x, float y) {
2613
2614        return hitTestChar(x, y, getNaturalBounds());
2615    }
2616
2617    /**
2618     * Returns the hash code of this <code>TextLayout</code>.
2619     * @return the hash code of this <code>TextLayout</code>.
2620     */

2621    public int hashCode() {
2622        if (hashCodeCache == 0) {
2623        ensureCache();
2624            hashCodeCache = textLine.hashCode();
2625        }
2626        return hashCodeCache;
2627    }
2628
2629    /**
2630     * Returns <code>true</code> if the specified <code>Object</code> is a
2631     * <code>TextLayout</code> object and if the specified <code>Object</code>
2632     * equals this <code>TextLayout</code>.
2633     * @param obj an <code>Object</code> to test for equality
2634     * @return <code>true</code> if the specified <code>Object</code>
2635     * equals this <code>TextLayout</code>; <code>false</code>
2636     * otherwise.
2637     */

2638    public boolean equals(Object JavaDoc obj) {
2639        return (obj instanceof TextLayout JavaDoc) && equals((TextLayout JavaDoc)obj);
2640    }
2641
2642    /**
2643     * Returns <code>true</code> if the two layouts are equal.
2644     * Two layouts are equal if they contain equal glyphvectors in the same order.
2645     * @param rhs the <code>TextLayout</code> to compare to this
2646     * <code>TextLayout</code>
2647     * @return <code>true</code> if the specified <code>TextLayout</code>
2648     * equals this <code>TextLayout</code>.
2649     *
2650     */

2651    public boolean equals(TextLayout JavaDoc rhs) {
2652
2653        if (rhs == null) {
2654            return false;
2655        }
2656        if (rhs == this) {
2657            return true;
2658        }
2659
2660    ensureCache();
2661        return textLine.equals(rhs.textLine);
2662    }
2663
2664    /**
2665     * Returns debugging information for this <code>TextLayout</code>.
2666     * @return the <code>textLine</code> of this <code>TextLayout</code>
2667     * as a <code>String</code>.
2668     */

2669    public String JavaDoc toString() {
2670    ensureCache();
2671        return textLine.toString();
2672     }
2673
2674    /**
2675     * Renders this <code>TextLayout</code> at the specified location in
2676     * the specified {@link java.awt.Graphics2D Graphics2D} context.
2677     * The origin of the layout is placed at x,&nbsp;y. Rendering may touch
2678     * any point within <code>getBounds()</code> of this position. This
2679     * leaves the <code>g2</code> unchanged.
2680     * @param g2 the <code>Graphics2D</code> context into which to render
2681     * the layout
2682     * @param x,&nbsp;y the coordinates of the origin of this
2683     * <code>TextLayout</code>
2684     * @see #getBounds()
2685     */

2686    public void draw(Graphics2D JavaDoc g2, float x, float y) {
2687
2688        if (g2 == null) {
2689            throw new IllegalArgumentException JavaDoc("Null Graphics2D passed to TextLayout.draw()");
2690        }
2691
2692        if (optInfo != null) {
2693            if (optInfo.draw(g2, x, y)) { // might fail to draw because of frc change
2694
return;
2695            }
2696            // replace with TextLine and fall through
2697
initTextLine();
2698        }
2699        textLine.draw(g2, x - dx, y - dy);
2700    }
2701
2702    /**
2703     * Package-only method for testing ONLY. Please don't abuse.
2704     */

2705    TextLine JavaDoc getTextLineForTesting() {
2706
2707        return textLine;
2708    }
2709
2710    /**
2711     *
2712     * Return the index of the first character with a different baseline from the
2713     * character at start, or limit if all characters between start and limit have
2714     * the same baseline.
2715     */

2716    private static int sameBaselineUpTo(Font JavaDoc font, char[] text,
2717                                        int start, int limit) {
2718        // current implementation doesn't support multiple baselines
2719
return limit;
2720        /*
2721        byte bl = font.getBaselineFor(text[start++]);
2722        while (start < limit && font.getBaselineFor(text[start]) == bl) {
2723            ++start;
2724        }
2725        return start;
2726        */

2727    }
2728
2729    static byte getBaselineFromGraphic(GraphicAttribute JavaDoc graphic) {
2730
2731        byte alignment = (byte) graphic.getAlignment();
2732
2733        if (alignment == GraphicAttribute.BOTTOM_ALIGNMENT ||
2734                alignment == GraphicAttribute.TOP_ALIGNMENT) {
2735
2736            return (byte)GraphicAttribute.ROMAN_BASELINE;
2737        }
2738        else {
2739            return alignment;
2740        }
2741    }
2742
2743  /**
2744   * Returns a <code>Shape</code> representing the outline of this
2745   * <code>TextLayout</code>.
2746   * @param tx an optional {@link AffineTransform} to apply to the
2747   * outline of this <code>TextLayout</code>.
2748   * @return a <code>Shape</code> that is the outline of this
2749   * <code>TextLayout</code>.
2750   */

2751    public Shape JavaDoc getOutline(AffineTransform JavaDoc tx) {
2752    ensureCache();
2753        return textLine.getOutline(tx);
2754    }
2755}
2756
Popular Tags