1 /*
2  * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */

25
26 /*
27  * (C) Copyright Taligent, Inc. 1996 - 1997, All Rights Reserved
28  * (C) Copyright IBM Corp. 1996-2003, All Rights Reserved
29  *
30  * The original version of this source code and documentation is
31  * copyrighted and owned by Taligent, Inc., a wholly-owned subsidiary
32  * of IBM. These materials are provided under terms of a License
33  * Agreement between Taligent and Sun. This technology is protected
34  * by multiple US and International patents.
35  *
36  * This notice and attribution to Taligent may not be removed.
37  * Taligent is a registered trademark of Taligent, Inc.
38  *
39  */

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

239 public final class TextLayout implements Cloneable {
240
241     private int characterCount;
242     private boolean isVerticalLine = false;
243     private byte baseline;
244     private float[] baselineOffsets;  // why have these ?
245     private TextLine textLine;
246
247     // cached values computed from GlyphSets and set info:
248     // all are recomputed from scratch in buildCache()
249     private TextLine.TextLineMetrics lineMetrics = null;
250     private float visibleAdvance;
251
252     /*
253      * TextLayouts are supposedly immutable.  If you mutate a TextLayout under
254      * the covers (like the justification code does) you'll need to set this
255      * back to false.  Could be replaced with textLine != null <--> cacheIsValid.
256      */

257     private boolean cacheIsValid = false;
258
259
260     // This value is obtained from an attribute, and constrained to the
261     // interval [0,1].  If 0, the layout cannot be justified.
262     private float justifyRatio;
263
264     // If a layout is produced by justification, then that layout
265     // cannot be justified.  To enforce this constraint the
266     // justifyRatio of the justified layout is set to this value.
267     private static final float ALREADY_JUSTIFIED = -53.9f;
268
269     // dx and dy specify the distance between the TextLayout's origin
270     // and the origin of the leftmost GlyphSet (TextLayoutComponent,
271     // actually).  They were used for hanging punctuation support,
272     // which is no longer implemented.  Currently they are both always 0,
273     // and TextLayout is not guaranteed to work with non-zero dx, dy
274     // values right now.  They were left in as an aide and reminder to
275     // anyone who implements hanging punctuation or other similar stuff.
276     // They are static now so they don't take up space in TextLayout
277     // instances.
278     private static float dx;
279     private static float dy;
280
281     /*
282      * Natural bounds is used internally.  It is built on demand in
283      * getNaturalBounds.
284      */

285     private Rectangle2D naturalBounds = null;
286
287     /*
288      * boundsRect encloses all of the bits this TextLayout can draw.  It
289      * is build on demand in getBounds.
290      */

291     private Rectangle2D boundsRect = null;
292
293     /*
294      * flag to suppress/allow carets inside of ligatures when hit testing or
295      * arrow-keying
296      */

297     private boolean caretsInLigaturesAreAllowed = false;
298
299     /**
300      * Defines a policy for determining the strong caret location.
301      * This class contains one method, {@code getStrongCaret}, which
302      * is used to specify the policy that determines the strong caret in
303      * dual-caret text.  The strong caret is used to move the caret to the
304      * left or right. Instances of this class can be passed to
305      * {@code getCaretShapes}, {@code getNextLeftHit} and
306      * {@code getNextRightHit} to customize strong caret
307      * selection.
308      * <p>
309      * To specify alternate caret policies, subclass {@code CaretPolicy}
310      * and override {@code getStrongCaret}.  {@code getStrongCaret}
311      * should inspect the two {@code TextHitInfo} arguments and choose
312      * one of them as the strong caret.
313      * <p>
314      * Most clients do not need to use this class.
315      */

316     public static class CaretPolicy {
317
318         /**
319          * Constructs a {@code CaretPolicy}.
320          */

321          public CaretPolicy() {
322          }
323
324         /**
325          * Chooses one of the specified {@code TextHitInfo} instances as
326          * a strong caret in the specified {@code TextLayout}.
327          * @param hit1 a valid hit in {@code layout}
328          * @param hit2 a valid hit in {@code layout}
329          * @param layout the {@code TextLayout} in which
330          *        {@code hit1} and {@code hit2} are used
331          * @return {@code hit1} or {@code hit2}
332          *        (or an equivalent {@code TextHitInfo}), indicating the
333          *        strong caret.
334          */

335         public TextHitInfo getStrongCaret(TextHitInfo hit1,
336                                           TextHitInfo hit2,
337                                           TextLayout layout) {
338
339             // default implementation just calls private method on layout
340             return layout.getStrongHit(hit1, hit2);
341         }
342     }
343
344     /**
345      * This {@code CaretPolicy} is used when a policy is not specified
346      * by the client.  With this policy, a hit on a character whose direction
347      * is the same as the line direction is stronger than a hit on a
348      * counterdirectional character.  If the characters' directions are
349      * the same, a hit on the leading edge of a character is stronger
350      * than a hit on the trailing edge of a character.
351      */

352     public static final CaretPolicy DEFAULT_CARET_POLICY = new CaretPolicy();
353
354     /**
355      * Constructs a {@code TextLayout} from a {@code String}
356      * and a {@link Font}.  All the text is styled using the specified
357      * {@code Font}.
358      * <p>
359      * The {@code String} must specify a single paragraph of text,
360      * because an entire paragraph is required for the bidirectional
361      * algorithm.
362      * @param string the text to display
363      * @param font a {@code Font} used to style the text
364      * @param frc contains information about a graphics device which is needed
365      *       to measure the text correctly.
366      *       Text measurements can vary slightly depending on the
367      *       device resolution, and attributes such as antialiasing.  This
368      *       parameter does not specify a translation between the
369      *       {@code TextLayout} and user space.
370      */

371     public TextLayout(String string, Font font, FontRenderContext frc) {
372
373         if (font == null) {
374             throw new IllegalArgumentException("Null font passed to TextLayout constructor.");
375         }
376
377         if (string == null) {
378             throw new IllegalArgumentException("Null string passed to TextLayout constructor.");
379         }
380
381         if (string.length() == 0) {
382             throw new IllegalArgumentException("Zero length string passed to TextLayout constructor.");
383         }
384
385         Map<? extends Attribute, ?> attributes = null;
386         if (font.hasLayoutAttributes()) {
387             attributes = font.getAttributes();
388         }
389
390         char[] text = string.toCharArray();
391         if (sameBaselineUpTo(font, text, 0, text.length) == text.length) {
392             fastInit(text, font, attributes, frc);
393         } else {
394             AttributedString as = attributes == null
395                 ? new AttributedString(string)
396                 : new AttributedString(string, attributes);
397             as.addAttribute(TextAttribute.FONT, font);
398             standardInit(as.getIterator(), text, frc);
399         }
400     }
401
402     /**
403      * Constructs a {@code TextLayout} from a {@code String}
404      * and an attribute set.
405      * <p>
406      * All the text is styled using the provided attributes.
407      * <p>
408      * {@code string} must specify a single paragraph of text because an
409      * entire paragraph is required for the bidirectional algorithm.
410      * @param string the text to display
411      * @param attributes the attributes used to style the text
412      * @param frc contains information about a graphics device which is needed
413      *       to measure the text correctly.
414      *       Text measurements can vary slightly depending on the
415      *       device resolution, and attributes such as antialiasing.  This
416      *       parameter does not specify a translation between the
417      *       {@code TextLayout} and user space.
418      */

419     public TextLayout(String string, Map<? extends Attribute,?> attributes,
420                       FontRenderContext frc)
421     {
422         if (string == null) {
423             throw new IllegalArgumentException("Null string passed to TextLayout constructor.");
424         }
425
426         if (attributes == null) {
427             throw new IllegalArgumentException("Null map passed to TextLayout constructor.");
428         }
429
430         if (string.length() == 0) {
431             throw new IllegalArgumentException("Zero length string passed to TextLayout constructor.");
432         }
433
434         char[] text = string.toCharArray();
435         Font font = singleFont(text, 0, text.length, attributes);
436         if (font != null) {
437             fastInit(text, font, attributes, frc);
438         } else {
439             AttributedString as = new AttributedString(string, attributes);
440             standardInit(as.getIterator(), text, frc);
441         }
442     }
443
444     /*
445      * Determines a font for the attributes, and if a single font can render
446      * all the text on one baseline, return it, otherwise null.  If the
447      * attributes specify a font, assume it can display all the text without
448      * checking.
449      * If the AttributeSet contains an embedded graphic, return null.
450      */

451     private static Font singleFont(char[] text,
452                                    int start,
453                                    int limit,
454                                    Map<? extends Attribute, ?> attributes) {
455
456         if (attributes.get(TextAttribute.CHAR_REPLACEMENT) != null) {
457             return null;
458         }
459
460         Font font = null;
461         try {
462             font = (Font)attributes.get(TextAttribute.FONT);
463         }
464         catch (ClassCastException e) {
465         }
466         if (font == null) {
467             if (attributes.get(TextAttribute.FAMILY) != null) {
468                 font = Font.getFont(attributes);
469                 if (font.canDisplayUpTo(text, start, limit) != -1) {
470                     return null;
471                 }
472             } else {
473                 FontResolver resolver = FontResolver.getInstance();
474                 CodePointIterator iter = CodePointIterator.create(text, start, limit);
475                 int fontIndex = resolver.nextFontRunIndex(iter);
476                 if (iter.charIndex() == limit) {
477                     font = resolver.getFont(fontIndex, attributes);
478                 }
479             }
480         }
481
482         if (sameBaselineUpTo(font, text, start, limit) != limit) {
483             return null;
484         }
485
486         return font;
487     }
488
489     /**
490      * Constructs a {@code TextLayout} from an iterator over styled text.
491      * <p>
492      * The iterator must specify a single paragraph of text because an
493      * entire paragraph is required for the bidirectional
494      * algorithm.
495      * @param text the styled text to display
496      * @param frc contains information about a graphics device which is needed
497      *       to measure the text correctly.
498      *       Text measurements can vary slightly depending on the
499      *       device resolution, and attributes such as antialiasing.  This
500      *       parameter does not specify a translation between the
501      *       {@code TextLayout} and user space.
502      */

503     public TextLayout(AttributedCharacterIterator text, FontRenderContext frc) {
504
505         if (text == null) {
506             throw new IllegalArgumentException("Null iterator passed to TextLayout constructor.");
507         }
508
509         int start = text.getBeginIndex();
510         int limit = text.getEndIndex();
511         if (start == limit) {
512             throw new IllegalArgumentException("Zero length iterator passed to TextLayout constructor.");
513         }
514
515         int len = limit - start;
516         text.first();
517         char[] chars = new char[len];
518         int n = 0;
519         for (char c = text.first();
520              c != CharacterIterator.DONE;
521              c = text.next())
522         {
523             chars[n++] = c;
524         }
525
526         text.first();
527         if (text.getRunLimit() == limit) {
528
529             Map<? extends Attribute, ?> attributes = text.getAttributes();
530             Font font = singleFont(chars, 0, len, attributes);
531             if (font != null) {
532                 fastInit(chars, font, attributes, frc);
533                 return;
534             }
535         }
536
537         standardInit(text, chars, frc);
538     }
539
540     /**
541      * Creates a {@code TextLayout} from a {@link TextLine} and
542      * some paragraph data.  This method is used by {@link TextMeasurer}.
543      * @param textLine the line measurement attributes to apply to the
544      *       the resulting {@code TextLayout}
545      * @param baseline the baseline of the text
546      * @param baselineOffsets the baseline offsets for this
547      * {@code TextLayout}.  This should already be normalized to
548      * {@code baseline}
549      * @param justifyRatio {@code 0} if the {@code TextLayout}
550      *     cannot be justified; {@code 1} otherwise.
551      */

552     TextLayout(TextLine textLine,
553                byte baseline,
554                float[] baselineOffsets,
555                float justifyRatio) {
556
557         this.characterCount = textLine.characterCount();
558         this.baseline = baseline;
559         this.baselineOffsets = baselineOffsets;
560         this.textLine = textLine;
561         this.justifyRatio = justifyRatio;
562     }
563
564     /**
565      * Initialize the paragraph-specific data.
566      */

567     private void paragraphInit(byte aBaseline, CoreMetrics lm,
568                                Map<? extends Attribute, ?> paragraphAttrs,
569                                char[] text) {
570
571         baseline = aBaseline;
572
573         // normalize to current baseline
574         baselineOffsets = TextLine.getNormalizedOffsets(lm.baselineOffsets, baseline);
575
576         justifyRatio = AttributeValues.getJustification(paragraphAttrs);
577         NumericShaper shaper = AttributeValues.getNumericShaping(paragraphAttrs);
578         if (shaper != null) {
579             shaper.shape(text, 0, text.length);
580         }
581     }
582
583     /*
584      * the fast init generates a single glyph set.  This requires:
585      * all one style
586      * all renderable by one font (ie no embedded graphics)
587      * all on one baseline
588      */

589     private void fastInit(char[] chars, Font font,
590                           Map<? extends Attribute, ?> attrs,
591                           FontRenderContext frc) {
592
593         // Object vf = attrs.get(TextAttribute.ORIENTATION);
594         // isVerticalLine = TextAttribute.ORIENTATION_VERTICAL.equals(vf);
595         isVerticalLine = false;
596
597         LineMetrics lm = font.getLineMetrics(chars, 0, chars.length, frc);
598         CoreMetrics cm = CoreMetrics.get(lm);
599         byte glyphBaseline = (byte) cm.baselineIndex;
600
601         if (attrs == null) {
602             baseline = glyphBaseline;
603             baselineOffsets = cm.baselineOffsets;
604             justifyRatio = 1.0f;
605         } else {
606             paragraphInit(glyphBaseline, cm, attrs, chars);
607         }
608
609         characterCount = chars.length;
610
611         textLine = TextLine.fastCreateTextLine(frc, chars, font, cm, attrs);
612     }
613
614     /*
615      * the standard init generates multiple glyph sets based on style,
616      * renderable, and baseline runs.
617      * @param chars the text in the iterator, extracted into a char array
618      */

619     private void standardInit(AttributedCharacterIterator text, char[] chars, FontRenderContext frc) {
620
621         characterCount = chars.length;
622
623         // set paragraph attributes
624         {
625             // If there's an embedded graphic at the start of the
626             // paragraph, look for the first non-graphic character
627             // and use it and its font to initialize the paragraph.
628             // If not, use the first graphic to initialize.
629
630             Map<? extends Attribute, ?> paragraphAttrs = text.getAttributes();
631
632             boolean haveFont = TextLine.advanceToFirstFont(text);
633
634             if (haveFont) {
635                 Font defaultFont = TextLine.getFontAtCurrentPos(text);
636                 int charsStart = text.getIndex() - text.getBeginIndex();
637                 LineMetrics lm = defaultFont.getLineMetrics(chars, charsStart, charsStart+1, frc);
638                 CoreMetrics cm = CoreMetrics.get(lm);
639                 paragraphInit((byte)cm.baselineIndex, cm, paragraphAttrs, chars);
640             }
641             else {
642                 // hmmm what to do here?  Just try to supply reasonable
643                 // values I guess.
644
645                 GraphicAttribute graphic = (GraphicAttribute)
646                                 paragraphAttrs.get(TextAttribute.CHAR_REPLACEMENT);
647                 byte defaultBaseline = getBaselineFromGraphic(graphic);
648                 CoreMetrics cm = GraphicComponent.createCoreMetrics(graphic);
649                 paragraphInit(defaultBaseline, cm, paragraphAttrs, chars);
650             }
651         }
652
653         textLine = TextLine.standardCreateTextLine(frc, text, chars, baselineOffsets);
654     }
655
656     /*
657      * A utility to rebuild the ascent/descent/leading/advance cache.
658      * You'll need to call this if you clone and mutate (like justification,
659      * editing methods do)
660      */

661     private void ensureCache() {
662         if (!cacheIsValid) {
663             buildCache();
664         }
665     }
666
667     private void buildCache() {
668         lineMetrics = textLine.getMetrics();
669
670         // compute visibleAdvance
671         if (textLine.isDirectionLTR()) {
672
673             int lastNonSpace = characterCount-1;
674             while (lastNonSpace != -1) {
675                 int logIndex = textLine.visualToLogical(lastNonSpace);
676                 if (!textLine.isCharSpace(logIndex)) {
677                     break;
678                 }
679                 else {
680                     --lastNonSpace;
681                 }
682             }
683             if (lastNonSpace == characterCount-1) {
684                 visibleAdvance = lineMetrics.advance;
685             }
686             else if (lastNonSpace == -1) {
687                 visibleAdvance = 0;
688             }
689             else {
690                 int logIndex = textLine.visualToLogical(lastNonSpace);
691                 visibleAdvance = textLine.getCharLinePosition(logIndex)
692                                         + textLine.getCharAdvance(logIndex);
693             }
694         }
695         else {
696
697             int leftmostNonSpace = 0;
698             while (leftmostNonSpace != characterCount) {
699                 int logIndex = textLine.visualToLogical(leftmostNonSpace);
700                 if (!textLine.isCharSpace(logIndex)) {
701                     break;
702                 }
703                 else {
704                     ++leftmostNonSpace;
705                 }
706             }
707             if (leftmostNonSpace == characterCount) {
708                 visibleAdvance = 0;
709             }
710             else if (leftmostNonSpace == 0) {
711                 visibleAdvance = lineMetrics.advance;
712             }
713             else {
714                 int logIndex = textLine.visualToLogical(leftmostNonSpace);
715                 float pos = textLine.getCharLinePosition(logIndex);
716                 visibleAdvance = lineMetrics.advance - pos;
717             }
718         }
719
720         // naturalBounds, boundsRect will be generated on demand
721         naturalBounds = null;
722         boundsRect = null;
723
724         cacheIsValid = true;
725     }
726
727     /**
728      * The 'natural bounds' encloses all the carets the layout can draw.
729      *
730      */

731     private Rectangle2D getNaturalBounds() {
732         ensureCache();
733
734         if (naturalBounds == null) {
735             naturalBounds = textLine.getItalicBounds();
736         }
737
738         return naturalBounds;
739     }
740
741     /**
742      * Creates a copy of this {@code TextLayout}.
743      */

744     protected Object clone() {
745         /*
746          * !!! I think this is safe.  Once created, nothing mutates the
747          * glyphvectors or arrays.  But we need to make sure.
748          * {jbr} actually, that's not quite true.  The justification code
749          * mutates after cloning.  It doesn't actually change the glyphvectors
750          * (that's impossible) but it replaces them with justified sets.  This
751          * is a problem for GlyphIterator creation, since new GlyphIterators
752          * are created by cloning a prototype.  If the prototype has outdated
753          * glyphvectors, so will the new ones.  A partial solution is to set the
754          * prototypical GlyphIterator to null when the glyphvectors change.  If
755          * you forget this one time, you're hosed.
756          */

757         try {
758             return super.clone();
759         }
760         catch (CloneNotSupportedException e) {
761             throw new InternalError(e);
762         }
763     }
764
765     /*
766      * Utility to throw an exception if an invalid TextHitInfo is passed
767      * as a parameter.  Avoids code duplication.
768      */

769     private void checkTextHit(TextHitInfo hit) {
770         if (hit == null) {
771             throw new IllegalArgumentException("TextHitInfo is null.");
772         }
773
774         if (hit.getInsertionIndex() < 0 ||
775             hit.getInsertionIndex() > characterCount) {
776             throw new IllegalArgumentException("TextHitInfo is out of range");
777         }
778     }
779
780     /**
781      * Creates a copy of this {@code TextLayout} justified to the
782      * specified width.
783      * <p>
784      * If this {@code TextLayout} has already been justified, an
785      * exception is thrown.  If this {@code TextLayout} object's
786      * justification ratio is zero, a {@code TextLayout} identical
787      * to this {@code TextLayout} is returned.
788      * @param justificationWidth the width to use when justifying the line.
789      * For best results, it should not be too different from the current
790      * advance of the line.
791      * @return a {@code TextLayout} justified to the specified width.
792      * @exception Error if this layout has already been justified, an Error is
793      * thrown.
794      */

795     public TextLayout getJustifiedLayout(float justificationWidth) {
796
797         if (justificationWidth <= 0) {
798             throw new IllegalArgumentException("justificationWidth <= 0 passed to TextLayout.getJustifiedLayout()");
799         }
800
801         if (justifyRatio == ALREADY_JUSTIFIED) {
802             throw new Error("Can't justify again.");
803         }
804
805         ensureCache(); // make sure textLine is not null
806
807         // default justification range to exclude trailing logical whitespace
808         int limit = characterCount;
809         while (limit > 0 && textLine.isCharWhitespace(limit-1)) {
810             --limit;
811         }
812
813         TextLine newLine = textLine.getJustifiedLine(justificationWidth, justifyRatio, 0, limit);
814         if (newLine != null) {
815             return new TextLayout(newLine, baseline, baselineOffsets, ALREADY_JUSTIFIED);
816         }
817
818         return this;
819     }
820
821     /**
822      * Justify this layout.  Overridden by subclassers to control justification
823      * (if there were subclassers, that is...)
824      *
825      * The layout will only justify if the paragraph attributes (from the
826      * source text, possibly defaulted by the layout attributes) indicate a
827      * non-zero justification ratio.  The text will be justified to the
828      * indicated width.  The current implementation also adjusts hanging
829      * punctuation and trailing whitespace to overhang the justification width.
830      * Once justified, the layout may not be rejustified.
831      * <p>
832      * Some code may rely on immutability of layouts.  Subclassers should not
833      * call this directly, but instead should call getJustifiedLayout, which
834      * will call this method on a clone of this layout, preserving
835      * the original.
836      *
837      * @param justificationWidth the width to use when justifying the line.
838      * For best results, it should not be too different from the current
839      * advance of the line.
840      * @see #getJustifiedLayout(float)
841      */

842     protected void handleJustify(float justificationWidth) {
843       // never called
844     }
845
846
847     /**
848      * Returns the baseline for this {@code TextLayout}.
849      * The baseline is one of the values defined in {@code Font},
850      * which are roman, centered and hanging.  Ascent and descent are
851      * relative to this baseline.  The {@code baselineOffsets}
852      * are also relative to this baseline.
853      * @return the baseline of this {@code TextLayout}.
854      * @see #getBaselineOffsets()
855      * @see Font
856      */

857     public byte getBaseline() {
858         return baseline;
859     }
860
861     /**
862      * Returns the offsets array for the baselines used for this
863      * {@code TextLayout}.
864      * <p>
865      * The array is indexed by one of the values defined in
866      * {@code Font}, which are roman, centered and hanging.  The
867      * values are relative to this {@code TextLayout} object's
868      * baseline, so that {@code getBaselineOffsets[getBaseline()] == 0}.
869      * Offsets are added to the position of the {@code TextLayout}
870      * object's baseline to get the position for the new baseline.
871      * @return the offsets array containing the baselines used for this
872      *    {@code TextLayout}.
873      * @see #getBaseline()
874      * @see Font
875      */

876     public float[] getBaselineOffsets() {
877         float[] offsets = new float[baselineOffsets.length];
878         System.arraycopy(baselineOffsets, 0, offsets, 0, offsets.length);
879         return offsets;
880     }
881
882     /**
883      * Returns the advance of this {@code TextLayout}.
884      * The advance is the distance from the origin to the advance of the
885      * rightmost (bottommost) character.  This is in baseline-relative
886      * coordinates.
887      * @return the advance of this {@code TextLayout}.
888      */

889     public float getAdvance() {
890         ensureCache();
891         return lineMetrics.advance;
892     }
893
894     /**
895      * Returns the advance of this {@code TextLayout}, minus trailing
896      * whitespace.  This is in baseline-relative coordinates.
897      * @return the advance of this {@code TextLayout} without the
898      *      trailing whitespace.
899      * @see #getAdvance()
900      */

901     public float getVisibleAdvance() {
902         ensureCache();
903         return visibleAdvance;
904     }
905
906     /**
907      * Returns the ascent of this {@code TextLayout}.
908      * The ascent is the distance from the top (right) of the
909      * {@code TextLayout} to the baseline.  It is always either
910      * positive or zero.  The ascent is sufficient to
911      * accommodate superscripted text and is the maximum of the sum of the
912      * ascent, offset, and baseline of each glyph.  The ascent is
913      * the maximum ascent from the baseline of all the text in the
914      * TextLayout.  It is in baseline-relative coordinates.
915      * @return the ascent of this {@code TextLayout}.
916      */

917     public float getAscent() {
918         ensureCache();
919         return lineMetrics.ascent;
920     }
921
922     /**
923      * Returns the descent of this {@code TextLayout}.
924      * The descent is the distance from the baseline to the bottom (left) of
925      * the {@code TextLayout}.  It is always either positive or zero.
926      * The descent is sufficient to accommodate subscripted text and is the
927      * maximum of the sum of the descent, offset, and baseline of each glyph.
928      * This is the maximum descent from the baseline of all the text in
929      * the TextLayout.  It is in baseline-relative coordinates.
930      * @return the descent of this {@code TextLayout}.
931      */

932     public float getDescent() {
933         ensureCache();
934         return lineMetrics.descent;
935     }
936
937     /**
938      * Returns the leading of the {@code TextLayout}.
939      * The leading is the suggested interline spacing for this
940      * {@code TextLayout}.  This is in baseline-relative
941      * coordinates.
942      * <p>
943      * The leading is computed from the leading, descent, and baseline
944      * of all glyphvectors in the {@code TextLayout}.  The algorithm
945      * is roughly as follows:
946      * <blockquote><pre>
947      * maxD = 0;
948      * maxDL = 0;
949      * for (GlyphVector g in all glyphvectors) {
950      *    maxD = max(maxD, g.getDescent() + offsets[g.getBaseline()]);
951      *    maxDL = max(maxDL, g.getDescent() + g.getLeading() +
952      *                       offsets[g.getBaseline()]);
953      * }
954      * return maxDL - maxD;
955      * </pre></blockquote>
956      * @return the leading of this {@code TextLayout}.
957      */

958     public float getLeading() {
959         ensureCache();
960         return lineMetrics.leading;
961     }
962
963     /**
964      * Returns the bounds of this {@code TextLayout}.
965      * The bounds are in standard coordinates.
966      * <p>Due to rasterization effects, this bounds might not enclose all of the
967      * pixels rendered by the TextLayout.</p>
968      * It might not coincide exactly with the ascent, descent,
969      * origin or advance of the {@code TextLayout}.
970      * @return a {@link Rectangle2D} that is the bounds of this
971      *        {@code TextLayout}.
972      */

973     public Rectangle2D getBounds() {
974         ensureCache();
975
976         if (boundsRect == null) {
977             Rectangle2D vb = textLine.getVisualBounds();
978             if (dx != 0 || dy != 0) {
979                 vb.setRect(vb.getX() - dx,
980                            vb.getY() - dy,
981                            vb.getWidth(),
982                            vb.getHeight());
983             }
984             boundsRect = vb;
985         }
986
987         Rectangle2D bounds = new Rectangle2D.Float();
988         bounds.setRect(boundsRect);
989
990         return bounds;
991     }
992
993     /**
994      * Returns the pixel bounds of this {@code TextLayout} when
995      * rendered in a graphics with the given
996      * {@code FontRenderContext} at the given location.  The
997      * graphics render context need not be the same as the
998      * {@code FontRenderContext} used to create this
999      * {@code TextLayout}, and can be null.  If it is null, the
1000      * {@code FontRenderContext} of this {@code TextLayout}
1001      * is used.
1002      * @param frc the {@code FontRenderContext} of the {@code Graphics}.
1003      * @param x the x-coordinate at which to render this {@code TextLayout}.
1004      * @param y the y-coordinate at which to render this {@code TextLayout}.
1005      * @return a {@code Rectangle} bounding the pixels that would be affected.
1006      * @see GlyphVector#getPixelBounds
1007      * @since 1.6
1008      */

1009     public Rectangle getPixelBounds(FontRenderContext frc, float x, float y) {
1010         return textLine.getPixelBounds(frc, x, y);
1011     }
1012
1013     /**
1014      * Returns {@code trueif this {@code TextLayout} has
1015      * a left-to-right base direction or {@code falseif it has
1016      * a right-to-left base direction.  The {@code TextLayout}
1017      * has a base direction of either left-to-right (LTR) or
1018      * right-to-left (RTL).  The base direction is independent of the
1019      * actual direction of text on the line, which may be either LTR,
1020      * RTL, or mixed. Left-to-right layouts by default should position
1021      * flush left.  If the layout is on a tabbed line, the
1022      * tabs run left to right, so that logically successive layouts position
1023      * left to right.  The opposite is true for RTL layouts. By default they
1024      * should position flush left, and tabs run right-to-left.
1025      * @return {@code trueif the base direction of this
1026      *         {@code TextLayout} is left-to-right; {@code false}
1027      *         otherwise.
1028      */

1029     public boolean isLeftToRight() {
1030         return textLine.isDirectionLTR();
1031     }
1032
1033     /**
1034      * Returns {@code trueif this {@code TextLayout} is vertical.
1035      * @return {@code trueif this {@code TextLayout} is vertical;
1036      *      {@code false} otherwise.
1037      */

1038     public boolean isVertical() {
1039         return isVerticalLine;
1040     }
1041
1042     /**
1043      * Returns the number of characters represented by this
1044      * {@code TextLayout}.
1045      * @return the number of characters in this {@code TextLayout}.
1046      */

1047     public int getCharacterCount() {
1048         return characterCount;
1049     }
1050
1051     /*
1052      * carets and hit testing
1053      *
1054      * Positions on a text line are represented by instances of TextHitInfo.
1055      * Any TextHitInfo with characterOffset between 0 and characterCount-1,
1056      * inclusive, represents a valid position on the line.  Additionally,
1057      * [-1, trailing] and [characterCount, leading] are valid positions, and
1058      * represent positions at the logical start and end of the line,
1059      * respectively.
1060      *
1061      * The characterOffsets in TextHitInfo's used and returned by TextLayout
1062      * are relative to the beginning of the text layout, not necessarily to
1063      * the beginning of the text storage the client is using.
1064      *
1065      *
1066      * Every valid TextHitInfo has either one or two carets associated with it.
1067      * A caret is a visual location in the TextLayout indicating where text at
1068      * the TextHitInfo will be displayed on screen.  If a TextHitInfo
1069      * represents a location on a directional boundary, then there are two
1070      * possible visible positions for newly inserted text.  Consider the
1071      * following example, in which capital letters indicate right-to-left text,
1072      * and the overall line direction is left-to-right:
1073      *
1074      * Text Storage: [ a, b, C, D, E, f ]
1075      * Display:        a b E D C f
1076      *
1077      * The text hit info (1, t) represents the trailing side of 'b'.  If 'q',
1078      * a left-to-right character is inserted into the text storage at this
1079      * location, it will be displayed between the 'b' and the 'E':
1080      *
1081      * Text Storage: [ a, b, q, C, D, E, f ]
1082      * Display:        a b q E D C f
1083      *
1084      * However, if a 'W', which is right-to-left, is inserted into the storage
1085      * after 'b', the storage and display will be:
1086      *
1087      * Text Storage: [ a, b, W, C, D, E, f ]
1088      * Display:        a b E D C W f
1089      *
1090      * So, for the original text storage, two carets should be displayed for
1091      * location (1, t): one visually between 'b' and 'E' and one visually
1092      * between 'C' and 'f'.
1093      *
1094      *
1095      * When two carets are displayed for a TextHitInfo, one caret is the
1096      * 'strong' caret and the other is the 'weak' caret.  The strong caret
1097      * indicates where an inserted character will be displayed when that
1098      * character's direction is the same as the direction of the TextLayout.
1099      * The weak caret shows where an character inserted character will be
1100      * displayed when the character's direction is opposite that of the
1101      * TextLayout.
1102      *
1103      *
1104      * Clients should not be overly concerned with the details of correct
1105      * caret display. TextLayout.getCaretShapes(TextHitInfo) will return an
1106      * array of two paths representing where carets should be displayed.
1107      * The first path in the array is the strong caret; the second element,
1108      * if non-null, is the weak caret.  If the second element is null,
1109      * then there is no weak caret for the given TextHitInfo.
1110      *
1111      *
1112      * Since text can be visually reordered, logically consecutive
1113      * TextHitInfo's may not be visually consecutive.  One implication of this
1114      * is that a client cannot tell from inspecting a TextHitInfo whether the
1115      * hit represents the first (or last) caret in the layout.  Clients
1116      * can call getVisualOtherHit();  if the visual companion is
1117      * (-1, TRAILING) or (characterCount, LEADING), then the hit is at the
1118      * first (last) caret position in the layout.
1119      */

1120
1121     private float[] getCaretInfo(int caret,
1122                                  Rectangle2D bounds,
1123                                  float[] info) {
1124
1125         float top1X, top2X;
1126         float bottom1X, bottom2X;
1127
1128         if (caret == 0 || caret == characterCount) {
1129
1130             float pos;
1131             int logIndex;
1132             if (caret == characterCount) {
1133                 logIndex = textLine.visualToLogical(characterCount-1);
1134                 pos = textLine.getCharLinePosition(logIndex)
1135                                         + textLine.getCharAdvance(logIndex);
1136             }
1137             else {
1138                 logIndex = textLine.visualToLogical(caret);
1139                 pos = textLine.getCharLinePosition(logIndex);
1140             }
1141             float angle = textLine.getCharAngle(logIndex);
1142             float shift = textLine.getCharShift(logIndex);
1143             pos += angle * shift;
1144             top1X = top2X = pos + angle*textLine.getCharAscent(logIndex);
1145             bottom1X = bottom2X = pos - angle*textLine.getCharDescent(logIndex);
1146         }
1147         else {
1148
1149             {
1150                 int logIndex = textLine.visualToLogical(caret-1);
1151                 float angle1 = textLine.getCharAngle(logIndex);
1152                 float pos1 = textLine.getCharLinePosition(logIndex)
1153                                     + textLine.getCharAdvance(logIndex);
1154                 if (angle1 != 0) {
1155                     pos1 += angle1 * textLine.getCharShift(logIndex);
1156                     top1X = pos1 + angle1*textLine.getCharAscent(logIndex);
1157                     bottom1X = pos1 - angle1*textLine.getCharDescent(logIndex);
1158                 }
1159                 else {
1160                     top1X = bottom1X = pos1;
1161                 }
1162             }
1163             {
1164                 int logIndex = textLine.visualToLogical(caret);
1165                 float angle2 = textLine.getCharAngle(logIndex);
1166                 float pos2 = textLine.getCharLinePosition(logIndex);
1167                 if (angle2 != 0) {
1168                     pos2 += angle2*textLine.getCharShift(logIndex);
1169                     top2X = pos2 + angle2*textLine.getCharAscent(logIndex);
1170                     bottom2X = pos2 - angle2*textLine.getCharDescent(logIndex);
1171                 }
1172                 else {
1173                     top2X = bottom2X = pos2;
1174                 }
1175             }
1176         }
1177
1178         float topX = (top1X + top2X) / 2;
1179         float bottomX = (bottom1X + bottom2X) / 2;
1180
1181         if (info == null) {
1182             info = new float[2];
1183         }
1184
1185         if (isVerticalLine) {
1186             info[1] = (float) ((topX - bottomX) / bounds.getWidth());
1187             info[0] = (float) (topX + (info[1]*bounds.getX()));
1188         }
1189         else {
1190             info[1] = (float) ((topX - bottomX) / bounds.getHeight());
1191             info[0] = (float) (bottomX + (info[1]*bounds.getMaxY()));
1192         }
1193
1194         return info;
1195     }
1196
1197     /**
1198      * Returns information about the caret corresponding to {@code hit}.
1199      * The first element of the array is the intersection of the caret with
1200      * the baseline, as a distance along the baseline. The second element
1201      * of the array is the inverse slope (run/rise) of the caret, measured
1202      * with respect to the baseline at that point.
1203      * <p>
1204      * This method is meant for informational use.  To display carets, it
1205      * is better to use {@code getCaretShapes}.
1206      * @param hit a hit on a character in this {@code TextLayout}
1207      * @param bounds the bounds to which the caret info is constructed.
1208      *     The bounds is in baseline-relative coordinates.
1209      * @return a two-element array containing the position and slope of
1210      * the caret.  The returned caret info is in baseline-relative coordinates.
1211      * @see #getCaretShapes(int, Rectangle2D, TextLayout.CaretPolicy)
1212      * @see Font#getItalicAngle
1213      */

1214     public float[] getCaretInfo(TextHitInfo hit, Rectangle2D bounds) {
1215         ensureCache();
1216         checkTextHit(hit);
1217
1218         return getCaretInfoTestInternal(hit, bounds);
1219     }
1220
1221     // this version provides extra info in the float array
1222     // the first two values are as above
1223     // the next four values are the endpoints of the caret, as computed
1224     // using the hit character's offset (baseline + ssoffset) and
1225     // natural ascent and descent.
1226     // these  values are trimmed to the bounds where required to fit,
1227     // but otherwise independent of it.
1228     private float[] getCaretInfoTestInternal(TextHitInfo hit, Rectangle2D bounds) {
1229         ensureCache();
1230         checkTextHit(hit);
1231
1232         float[] info = new float[6];
1233
1234         // get old data first
1235         getCaretInfo(hitToCaret(hit), bounds, info);
1236
1237         // then add our new data
1238         double iangle, ixbase, p1x, p1y, p2x, p2y;
1239
1240         int charix = hit.getCharIndex();
1241         boolean lead = hit.isLeadingEdge();
1242         boolean ltr = textLine.isDirectionLTR();
1243         boolean horiz = !isVertical();
1244
1245         if (charix == -1 || charix == characterCount) {
1246             // !!! note: want non-shifted, baseline ascent and descent here!
1247             // TextLine should return appropriate line metrics object for these values
1248             TextLineMetrics m = textLine.getMetrics();
1249             boolean low = ltr == (charix == -1);
1250             iangle = 0;
1251             if (horiz) {
1252                 p1x = p2x = low ? 0 : m.advance;
1253                 p1y = -m.ascent;
1254                 p2y = m.descent;
1255             } else {
1256                 p1y = p2y = low ? 0 : m.advance;
1257                 p1x = m.descent;
1258                 p2x = m.ascent;
1259             }
1260         } else {
1261             CoreMetrics thiscm = textLine.getCoreMetricsAt(charix);
1262             iangle = thiscm.italicAngle;
1263             ixbase = textLine.getCharLinePosition(charix, lead);
1264             if (thiscm.baselineIndex < 0) {
1265                 // this is a graphic, no italics, use entire line height for caret
1266                 TextLineMetrics m = textLine.getMetrics();
1267                 if (horiz) {
1268                     p1x = p2x = ixbase;
1269                     if (thiscm.baselineIndex == GraphicAttribute.TOP_ALIGNMENT) {
1270                         p1y = -m.ascent;
1271                         p2y = p1y + thiscm.height;
1272                     } else {
1273                         p2y = m.descent;
1274                         p1y = p2y - thiscm.height;
1275                     }
1276                 } else {
1277                     p1y = p2y = ixbase;
1278                     p1x = m.descent;
1279                     p2x = m.ascent;
1280                     // !!! top/bottom adjustment not implemented for vertical
1281                 }
1282             } else {
1283                 float bo = baselineOffsets[thiscm.baselineIndex];
1284                 if (horiz) {
1285                     ixbase += iangle * thiscm.ssOffset;
1286                     p1x = ixbase + iangle * thiscm.ascent;
1287                     p2x = ixbase - iangle * thiscm.descent;
1288                     p1y = bo - thiscm.ascent;
1289                     p2y = bo + thiscm.descent;
1290                 } else {
1291                     ixbase -= iangle * thiscm.ssOffset;
1292                     p1y = ixbase + iangle * thiscm.ascent;
1293                     p2y = ixbase - iangle * thiscm.descent;
1294                     p1x = bo + thiscm.ascent;
1295                     p2x = bo + thiscm.descent;
1296                 }
1297             }
1298         }
1299
1300         info[2] = (float)p1x;
1301         info[3] = (float)p1y;
1302         info[4] = (float)p2x;
1303         info[5] = (float)p2y;
1304
1305         return info;
1306     }
1307
1308     /**
1309      * Returns information about the caret corresponding to {@code hit}.
1310      * This method is a convenience overload of {@code getCaretInfo} and
1311      * uses the natural bounds of this {@code TextLayout}.
1312      * @param hit a hit on a character in this {@code TextLayout}
1313      * @return the information about a caret corresponding to a hit.  The
1314      *     returned caret info is in baseline-relative coordinates.
1315      */

1316     public float[] getCaretInfo(TextHitInfo hit) {
1317
1318         return getCaretInfo(hit, getNaturalBounds());
1319     }
1320
1321     /**
1322      * Returns a caret index corresponding to {@code hit}.
1323      * Carets are numbered from left to right (top to bottom) starting from
1324      * zero. This always places carets next to the character hit, on the
1325      * indicated side of the character.
1326      * @param hit a hit on a character in this {@code TextLayout}
1327      * @return a caret index corresponding to the specified hit.
1328      */

1329     private int hitToCaret(TextHitInfo hit) {
1330
1331         int hitIndex = hit.getCharIndex();
1332
1333         if (hitIndex < 0) {
1334             return textLine.isDirectionLTR() ? 0 : characterCount;
1335         } else if (hitIndex >= characterCount) {
1336             return textLine.isDirectionLTR() ? characterCount : 0;
1337         }
1338
1339         int visIndex = textLine.logicalToVisual(hitIndex);
1340
1341         if (hit.isLeadingEdge() != textLine.isCharLTR(hitIndex)) {
1342             ++visIndex;
1343         }
1344
1345         return visIndex;
1346     }
1347
1348     /**
1349      * Given a caret index, return a hit whose caret is at the index.
1350      * The hit is NOT guaranteed to be strong!!!
1351      *
1352      * @param caret a caret index.
1353      * @return a hit on this layout whose strong caret is at the requested
1354      * index.
1355      */

1356     private TextHitInfo caretToHit(int caret) {
1357
1358         if (caret == 0 || caret == characterCount) {
1359
1360             if ((caret == characterCount) == textLine.isDirectionLTR()) {
1361                 return TextHitInfo.leading(characterCount);
1362             }
1363             else {
1364                 return TextHitInfo.trailing(-1);
1365             }
1366         }
1367         else {
1368
1369             int charIndex = textLine.visualToLogical(caret);
1370             boolean leading = textLine.isCharLTR(charIndex);
1371
1372             return leading? TextHitInfo.leading(charIndex)
1373                             : TextHitInfo.trailing(charIndex);
1374         }
1375     }
1376
1377     private boolean caretIsValid(int caret) {
1378
1379         if (caret == characterCount || caret == 0) {
1380             return true;
1381         }
1382
1383         int offset = textLine.visualToLogical(caret);
1384
1385         if (!textLine.isCharLTR(offset)) {
1386             offset = textLine.visualToLogical(caret-1);
1387             if (textLine.isCharLTR(offset)) {
1388                 return true;
1389             }
1390         }
1391
1392         // At this point, the leading edge of the character
1393         // at offset is at the given caret.
1394
1395         return textLine.caretAtOffsetIsValid(offset);
1396     }
1397
1398     /**
1399      * Returns the hit for the next caret to the right (bottom); if there
1400      * is no such hit, returns {@code null}.
1401      * If the hit character index is out of bounds, an
1402      * {@link IllegalArgumentException} is thrown.
1403      * @param hit a hit on a character in this layout
1404      * @return a hit whose caret appears at the next position to the
1405      * right (bottom) of the caret of the provided hit or {@code null}.
1406      */

1407     public TextHitInfo getNextRightHit(TextHitInfo hit) {
1408         ensureCache();
1409         checkTextHit(hit);
1410
1411         int caret = hitToCaret(hit);
1412
1413         if (caret == characterCount) {
1414             return null;
1415         }
1416
1417         do {
1418             ++caret;
1419         } while (!caretIsValid(caret));
1420
1421         return caretToHit(caret);
1422     }
1423
1424     /**
1425      * Returns the hit for the next caret to the right (bottom); if no
1426      * such hit, returns {@code null}.  The hit is to the right of
1427      * the strong caret at the specified offset, as determined by the
1428      * specified policy.
1429      * The returned hit is the stronger of the two possible
1430      * hits, as determined by the specified policy.
1431      * @param offset an insertion offset in this {@code TextLayout}.
1432      * Cannot be less than 0 or greater than this {@code TextLayout}
1433      * object's character count.
1434      * @param policy the policy used to select the strong caret
1435      * @return a hit whose caret appears at the next position to the
1436      * right (bottom) of the caret of the provided hit, or {@code null}.
1437      */

1438     public TextHitInfo getNextRightHit(int offset, CaretPolicy policy) {
1439
1440         if (offset < 0 || offset > characterCount) {
1441             throw new IllegalArgumentException("Offset out of bounds in TextLayout.getNextRightHit()");
1442         }
1443
1444         if (policy == null) {
1445             throw new IllegalArgumentException("Null CaretPolicy passed to TextLayout.getNextRightHit()");
1446         }
1447
1448         TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
1449         TextHitInfo hit2 = hit1.getOtherHit();
1450
1451         TextHitInfo nextHit = getNextRightHit(policy.getStrongCaret(hit1, hit2, this));
1452
1453         if (nextHit != null) {
1454             TextHitInfo otherHit = getVisualOtherHit(nextHit);
1455             return policy.getStrongCaret(otherHit, nextHit, this);
1456         }
1457         else {
1458             return null;
1459         }
1460     }
1461
1462     /**
1463      * Returns the hit for the next caret to the right (bottom); if no
1464      * such hit, returns {@code null}.  The hit is to the right of
1465      * the strong caret at the specified offset, as determined by the
1466      * default policy.
1467      * The returned hit is the stronger of the two possible
1468      * hits, as determined by the default policy.
1469      * @param offset an insertion offset in this {@code TextLayout}.
1470      * Cannot be less than 0 or greater than the {@code TextLayout}
1471      * object's character count.
1472      * @return a hit whose caret appears at the next position to the
1473      * right (bottom) of the caret of the provided hit, or {@code null}.
1474      */

1475     public TextHitInfo getNextRightHit(int offset) {
1476
1477         return getNextRightHit(offset, DEFAULT_CARET_POLICY);
1478     }
1479
1480     /**
1481      * Returns the hit for the next caret to the left (top); if no such
1482      * hit, returns {@code null}.
1483      * If the hit character index is out of bounds, an
1484      * {@code IllegalArgumentException} is thrown.
1485      * @param hit a hit on a character in this {@code TextLayout}.
1486      * @return a hit whose caret appears at the next position to the
1487      * left (top) of the caret of the provided hit, or {@code null}.
1488      */

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

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

1557     public TextHitInfo getNextLeftHit(int offset) {
1558
1559         return getNextLeftHit(offset, DEFAULT_CARET_POLICY);
1560     }
1561
1562     /**
1563      * Returns the hit on the opposite side of the specified hit's caret.
1564      * @param hit the specified hit
1565      * @return a hit that is on the opposite side of the specified hit's
1566      *    caret.
1567      */

1568     public TextHitInfo getVisualOtherHit(TextHitInfo hit) {
1569
1570         ensureCache();
1571         checkTextHit(hit);
1572
1573         int hitCharIndex = hit.getCharIndex();
1574
1575         int charIndex;
1576         boolean leading;
1577
1578         if (hitCharIndex == -1 || hitCharIndex == characterCount) {
1579
1580             int visIndex;
1581             if (textLine.isDirectionLTR() == (hitCharIndex == -1)) {
1582                 visIndex = 0;
1583             }
1584             else {
1585                 visIndex = characterCount-1;
1586             }
1587
1588             charIndex = textLine.visualToLogical(visIndex);
1589
1590             if (textLine.isDirectionLTR() == (hitCharIndex == -1)) {
1591                 // at left end
1592                 leading = textLine.isCharLTR(charIndex);
1593             }
1594             else {
1595                 // at right end
1596                 leading = !textLine.isCharLTR(charIndex);
1597             }
1598         }
1599         else {
1600
1601             int visIndex = textLine.logicalToVisual(hitCharIndex);
1602
1603             boolean movedToRight;
1604             if (textLine.isCharLTR(hitCharIndex) == hit.isLeadingEdge()) {
1605                 --visIndex;
1606                 movedToRight = false;
1607             }
1608             else {
1609                 ++visIndex;
1610                 movedToRight = true;
1611             }
1612
1613             if (visIndex > -1 && visIndex < characterCount) {
1614                 charIndex = textLine.visualToLogical(visIndex);
1615                 leading = movedToRight == textLine.isCharLTR(charIndex);
1616             }
1617             else {
1618                 charIndex =
1619                     (movedToRight == textLine.isDirectionLTR())? characterCount : -1;
1620                 leading = charIndex == characterCount;
1621             }
1622         }
1623
1624         return leading? TextHitInfo.leading(charIndex) :
1625                                 TextHitInfo.trailing(charIndex);
1626     }
1627
1628     private double[] getCaretPath(TextHitInfo hit, Rectangle2D bounds) {
1629         float[] info = getCaretInfo(hit, bounds);
1630         return new double[] { info[2], info[3], info[4], info[5] };
1631     }
1632
1633     /**
1634      * Return an array of four floats corresponding the endpoints of the caret
1635      * x0, y0, x1, y1.
1636      *
1637      * This creates a line along the slope of the caret intersecting the
1638      * baseline at the caret
1639      * position, and extending from ascent above the baseline to descent below
1640      * it.
1641      */

1642     private double[] getCaretPath(int caret, Rectangle2D bounds,
1643                                   boolean clipToBounds) {
1644
1645         float[] info = getCaretInfo(caret, bounds, null);
1646
1647         double pos = info[0];
1648         double slope = info[1];
1649
1650         double x0, y0, x1, y1;
1651         double x2 = -3141.59, y2 = -2.7; // values are there to make compiler happy
1652
1653         double left = bounds.getX();
1654         double right = left + bounds.getWidth();
1655         double top = bounds.getY();
1656         double bottom = top + bounds.getHeight();
1657
1658         boolean threePoints = false;
1659
1660         if (isVerticalLine) {
1661
1662             if (slope >= 0) {
1663                 x0 = left;
1664                 x1 = right;
1665             }
1666             else {
1667                 x1 = left;
1668                 x0 = right;
1669             }
1670
1671             y0 = pos + x0 * slope;
1672             y1 = pos + x1 * slope;
1673
1674             // y0 <= y1, always
1675
1676             if (clipToBounds) {
1677                 if (y0 < top) {
1678                     if (slope <= 0 || y1 <= top) {
1679                         y0 = y1 = top;
1680                     }
1681                     else {
1682                         threePoints = true;
1683                         y0 = top;
1684                         y2 = top;
1685                         x2 = x1 + (top-y1)/slope;
1686                         if (y1 > bottom) {
1687                             y1 = bottom;
1688                         }
1689                     }
1690                 }
1691                 else if (y1 > bottom) {
1692                     if (slope >= 0 || y0 >= bottom) {
1693                         y0 = y1 = bottom;
1694                     }
1695                     else {
1696                         threePoints = true;
1697                         y1 = bottom;
1698                         y2 = bottom;
1699                         x2 = x0 + (bottom-x1)/slope;
1700                     }
1701                 }
1702             }
1703
1704         }
1705         else {
1706
1707             if (slope >= 0) {
1708                 y0 = bottom;
1709                 y1 = top;
1710             }
1711             else {
1712                 y1 = bottom;
1713                 y0 = top;
1714             }
1715
1716             x0 = pos - y0 * slope;
1717             x1 = pos - y1 * slope;
1718
1719             // x0 <= x1, always
1720
1721             if (clipToBounds) {
1722                 if (x0 < left) {
1723                     if (slope <= 0 || x1 <= left) {
1724                         x0 = x1 = left;
1725                     }
1726                     else {
1727                         threePoints = true;
1728                         x0 = left;
1729                         x2 = left;
1730                         y2 = y1 - (left-x1)/slope;
1731                         if (x1 > right) {
1732                             x1 = right;
1733                         }
1734                     }
1735                 }
1736                 else if (x1 > right) {
1737                     if (slope >= 0 || x0 >= right) {
1738                         x0 = x1 = right;
1739                     }
1740                     else {
1741                         threePoints = true;
1742                         x1 = right;
1743                         x2 = right;
1744                         y2 = y0 - (right-x0)/slope;
1745                     }
1746                 }
1747             }
1748         }
1749
1750         return threePoints?
1751                     new double[] { x0, y0, x2, y2, x1, y1 } :
1752                     new double[] { x0, y0, x1, y1 };
1753     }
1754
1755
1756     private static GeneralPath pathToShape(double[] path, boolean close, LayoutPathImpl lp) {
1757         GeneralPath result = new GeneralPath(GeneralPath.WIND_EVEN_ODD, path.length);
1758         result.moveTo((float)path[0], (float)path[1]);
1759         for (int i = 2; i < path.length; i += 2) {
1760             result.lineTo((float)path[i], (float)path[i+1]);
1761         }
1762         if (close) {
1763             result.closePath();
1764         }
1765
1766         if (lp != null) {
1767             result = (GeneralPath)lp.mapShape(result);
1768         }
1769         return result;
1770     }
1771
1772     /**
1773      * Returns a {@link Shape} representing the caret at the specified
1774      * hit inside the specified bounds.
1775      * @param hit the hit at which to generate the caret
1776      * @param bounds the bounds of the {@code TextLayout} to use
1777      *    in generating the caret.  The bounds is in baseline-relative
1778      *    coordinates.
1779      * @return a {@code Shape} representing the caret.  The returned
1780      *    shape is in standard coordinates.
1781      */

1782     public Shape getCaretShape(TextHitInfo hit, Rectangle2D bounds) {
1783         ensureCache();
1784         checkTextHit(hit);
1785
1786         if (bounds == null) {
1787             throw new IllegalArgumentException("Null Rectangle2D passed to TextLayout.getCaret()");
1788         }
1789
1790         return pathToShape(getCaretPath(hit, bounds), false, textLine.getLayoutPath());
1791     }
1792
1793     /**
1794      * Returns a {@code Shape} representing the caret at the specified
1795      * hit inside the natural bounds of this {@code TextLayout}.
1796      * @param hit the hit at which to generate the caret
1797      * @return a {@code Shape} representing the caret.  The returned
1798      *     shape is in standard coordinates.
1799      */

1800     public Shape getCaretShape(TextHitInfo hit) {
1801
1802         return getCaretShape(hit, getNaturalBounds());
1803     }
1804
1805     /**
1806      * Return the "stronger" of the TextHitInfos.  The TextHitInfos
1807      * should be logical or visual counterparts.  They are not
1808      * checked for validity.
1809      */

1810     private TextHitInfo getStrongHit(TextHitInfo hit1, TextHitInfo hit2) {
1811
1812         // right now we're using the following rule for strong hits:
1813         // A hit on a character with a lower level
1814         // is stronger than one on a character with a higher level.
1815         // If this rule ties, the hit on the leading edge of a character wins.
1816         // If THIS rule ties, hit1 wins.  Both rules shouldn't tie, unless the
1817         // infos aren't counterparts of some sort.
1818
1819         byte hit1Level = getCharacterLevel(hit1.getCharIndex());
1820         byte hit2Level = getCharacterLevel(hit2.getCharIndex());
1821
1822         if (hit1Level == hit2Level) {
1823             if (hit2.isLeadingEdge() && !hit1.isLeadingEdge()) {
1824                 return hit2;
1825             }
1826             else {
1827                 return hit1;
1828             }
1829         }
1830         else {
1831             return (hit1Level < hit2Level)? hit1 : hit2;
1832         }
1833     }
1834
1835     /**
1836      * Returns the level of the character at {@code index}.
1837      * Indices -1 and {@code characterCount} are assigned the base
1838      * level of this {@code TextLayout}.
1839      * @param index the index of the character from which to get the level
1840      * @return the level of the character at the specified index.
1841      */

1842     public byte getCharacterLevel(int index) {
1843
1844         // hmm, allow indices at endpoints?  For now, yes.
1845         if (index < -1 || index > characterCount) {
1846             throw new IllegalArgumentException("Index is out of range in getCharacterLevel.");
1847         }
1848
1849         ensureCache();
1850         if (index == -1 || index == characterCount) {
1851              return (byte) (textLine.isDirectionLTR()? 0 : 1);
1852         }
1853
1854         return textLine.getCharLevel(index);
1855     }
1856
1857     /**
1858      * Returns two paths corresponding to the strong and weak caret.
1859      * @param offset an offset in this {@code TextLayout}
1860      * @param bounds the bounds to which to extend the carets.  The
1861      * bounds is in baseline-relative coordinates.
1862      * @param policy the specified {@code CaretPolicy}
1863      * @return an array of two paths.  Element zero is the strong
1864      * caret.  If there are two carets, element one is the weak caret,
1865      * otherwise it is {@code null}. The returned shapes
1866      * are in standard coordinates.
1867      */

1868     public Shape[] getCaretShapes(int offset, Rectangle2D bounds, CaretPolicy policy) {
1869
1870         ensureCache();
1871
1872         if (offset < 0 || offset > characterCount) {
1873             throw new IllegalArgumentException("Offset out of bounds in TextLayout.getCaretShapes()");
1874         }
1875
1876         if (bounds == null) {
1877             throw new IllegalArgumentException("Null Rectangle2D passed to TextLayout.getCaretShapes()");
1878         }
1879
1880         if (policy == null) {
1881             throw new IllegalArgumentException("Null CaretPolicy passed to TextLayout.getCaretShapes()");
1882         }
1883
1884         Shape[] result = new Shape[2];
1885
1886         TextHitInfo hit = TextHitInfo.afterOffset(offset);
1887
1888         int hitCaret = hitToCaret(hit);
1889
1890         LayoutPathImpl lp = textLine.getLayoutPath();
1891         Shape hitShape = pathToShape(getCaretPath(hit, bounds), false, lp);
1892         TextHitInfo otherHit = hit.getOtherHit();
1893         int otherCaret = hitToCaret(otherHit);
1894
1895         if (hitCaret == otherCaret) {
1896             result[0] = hitShape;
1897         }
1898         else { // more than one caret
1899             Shape otherShape = pathToShape(getCaretPath(otherHit, bounds), false, lp);
1900
1901             TextHitInfo strongHit = policy.getStrongCaret(hit, otherHit, this);
1902             boolean hitIsStrong = strongHit.equals(hit);
1903
1904             if (hitIsStrong) {// then other is weak
1905                 result[0] = hitShape;
1906                 result[1] = otherShape;
1907             }
1908             else {
1909                 result[0] = otherShape;
1910                 result[1] = hitShape;
1911             }
1912         }
1913
1914         return result;
1915     }
1916
1917     /**
1918      * Returns two paths corresponding to the strong and weak caret.
1919      * This method is a convenience overload of {@code getCaretShapes}
1920      * that uses the default caret policy.
1921      * @param offset an offset in this {@code TextLayout}
1922      * @param bounds the bounds to which to extend the carets.  This is
1923      *     in baseline-relative coordinates.
1924      * @return two paths corresponding to the strong and weak caret as
1925      *    defined by the {@code DEFAULT_CARET_POLICY}.  These are
1926      *    in standard coordinates.
1927      */

1928     public Shape[] getCaretShapes(int offset, Rectangle2D bounds) {
1929         // {sfb} parameter checking is done in overloaded version
1930         return getCaretShapes(offset, bounds, DEFAULT_CARET_POLICY);
1931     }
1932
1933     /**
1934      * Returns two paths corresponding to the strong and weak caret.
1935      * This method is a convenience overload of {@code getCaretShapes}
1936      * that uses the default caret policy and this {@code TextLayout}
1937      * object's natural bounds.
1938      * @param offset an offset in this {@code TextLayout}
1939      * @return two paths corresponding to the strong and weak caret as
1940      *    defined by the {@code DEFAULT_CARET_POLICY}.  These are
1941      *    in standard coordinates.
1942      */

1943     public Shape[] getCaretShapes(int offset) {
1944         // {sfb} parameter checking is done in overloaded version
1945         return getCaretShapes(offset, getNaturalBounds(), DEFAULT_CARET_POLICY);
1946     }
1947
1948     // A utility to return a path enclosing the given path
1949     // Path0 must be left or top of path1
1950     // {jbr} no assumptions about size of path0, path1 anymore.
1951     private GeneralPath boundingShape(double[] path0, double[] path1) {
1952
1953         // Really, we want the path to be a convex hull around all of the
1954         // points in path0 and path1.  But we can get by with less than
1955         // that.  We do need to prevent the two segments which
1956         // join path0 to path1 from crossing each other.  So, if we
1957         // traverse path0 from top to bottom, we'll traverse path1 from
1958         // bottom to top (and vice versa).
1959
1960         GeneralPath result = pathToShape(path0, falsenull);
1961
1962         boolean sameDirection;
1963
1964         if (isVerticalLine) {
1965             sameDirection = (path0[1] > path0[path0.length-1]) ==
1966                             (path1[1] > path1[path1.length-1]);
1967         }
1968         else {
1969             sameDirection = (path0[0] > path0[path0.length-2]) ==
1970                             (path1[0] > path1[path1.length-2]);
1971         }
1972
1973         int start;
1974         int limit;
1975         int increment;
1976
1977         if (sameDirection) {
1978             start = path1.length-2;
1979             limit = -2;
1980             increment = -2;
1981         }
1982         else {
1983             start = 0;
1984             limit = path1.length;
1985             increment = 2;
1986         }
1987
1988         for (int i = start; i != limit; i += increment) {
1989             result.lineTo((float)path1[i], (float)path1[i+1]);
1990         }
1991
1992         result.closePath();
1993
1994         return result;
1995     }
1996
1997     // A utility to convert a pair of carets into a bounding path
1998     // {jbr} Shape is never outside of bounds.
1999     private GeneralPath caretBoundingShape(int caret0,
2000                                            int caret1,
2001                                            Rectangle2D bounds) {
2002
2003         if (caret0 > caret1) {
2004             int temp = caret0;
2005             caret0 = caret1;
2006             caret1 = temp;
2007         }
2008
2009         return boundingShape(getCaretPath(caret0, bounds, true),
2010                              getCaretPath(caret1, bounds, true));
2011     }
2012
2013     /*
2014      * A utility to return the path bounding the area to the left (top) of the
2015      * layout.
2016      * Shape is never outside of bounds.
2017      */

2018     private GeneralPath leftShape(Rectangle2D bounds) {
2019
2020         double[] path0;
2021         if (isVerticalLine) {
2022             path0 = new double[] { bounds.getX(), bounds.getY(),
2023                                        bounds.getX() + bounds.getWidth(),
2024                                        bounds.getY() };
2025         } else {
2026             path0 = new double[] { bounds.getX(),
2027                                        bounds.getY() + bounds.getHeight(),
2028                                        bounds.getX(), bounds.getY() };
2029         }
2030
2031         double[] path1 = getCaretPath(0, bounds, true);
2032
2033         return boundingShape(path0, path1);
2034     }
2035
2036     /*
2037      * A utility to return the path bounding the area to the right (bottom) of
2038      * the layout.
2039      */

2040     private GeneralPath rightShape(Rectangle2D bounds) {
2041         double[] path1;
2042         if (isVerticalLine) {
2043             path1 = new double[] {
2044                 bounds.getX(),
2045                 bounds.getY() + bounds.getHeight(),
2046                 bounds.getX() + bounds.getWidth(),
2047                 bounds.getY() + bounds.getHeight()
2048             };
2049         } else {
2050             path1 = new double[] {
2051                 bounds.getX() + bounds.getWidth(),
2052                 bounds.getY() + bounds.getHeight(),
2053                 bounds.getX() + bounds.getWidth(),
2054                 bounds.getY()
2055             };
2056         }
2057
2058         double[] path0 = getCaretPath(characterCount, bounds, true);
2059
2060         return boundingShape(path0, path1);
2061     }
2062
2063     /**
2064      * Returns the logical ranges of text corresponding to a visual selection.
2065      * @param firstEndpoint an endpoint of the visual range
2066      * @param secondEndpoint the other endpoint of the visual range.
2067      * This endpoint can be less than {@code firstEndpoint}.
2068      * @return an array of integers representing start/limit pairs for the
2069      * selected ranges.
2070      * @see #getVisualHighlightShape(TextHitInfo, TextHitInfo, Rectangle2D)
2071      */

2072     public int[] getLogicalRangesForVisualSelection(TextHitInfo firstEndpoint,
2073                                                     TextHitInfo secondEndpoint) {
2074         ensureCache();
2075
2076         checkTextHit(firstEndpoint);
2077         checkTextHit(secondEndpoint);
2078
2079         // !!! probably want to optimize for all LTR text
2080
2081         boolean[] included = new boolean[characterCount];
2082
2083         int startIndex = hitToCaret(firstEndpoint);
2084         int limitIndex = hitToCaret(secondEndpoint);
2085
2086         if (startIndex > limitIndex) {
2087             int t = startIndex;
2088             startIndex = limitIndex;
2089             limitIndex = t;
2090         }
2091
2092         /*
2093          * now we have the visual indexes of the glyphs at the start and limit
2094          * of the selection range walk through runs marking characters that
2095          * were included in the visual range there is probably a more efficient
2096          * way to do this, but this ought to work, so hey
2097          */

2098
2099         if (startIndex < limitIndex) {
2100             int visIndex = startIndex;
2101             while (visIndex < limitIndex) {
2102                 included[textLine.visualToLogical(visIndex)] = true;
2103                 ++visIndex;
2104             }
2105         }
2106
2107         /*
2108          * count how many runs we have, ought to be one or two, but perhaps
2109          * things are especially weird
2110          */

2111         int count = 0;
2112         boolean inrun = false;
2113         for (int i = 0; i < characterCount; i++) {
2114             if (included[i] != inrun) {
2115                 inrun = !inrun;
2116                 if (inrun) {
2117                     count++;
2118                 }
2119             }
2120         }
2121
2122         int[] ranges = new int[count * 2];
2123         count = 0;
2124         inrun = false;
2125         for (int i = 0; i < characterCount; i++) {
2126             if (included[i] != inrun) {
2127                 ranges[count++] = i;
2128                 inrun = !inrun;
2129             }
2130         }
2131         if (inrun) {
2132             ranges[count++] = characterCount;
2133         }
2134
2135         return ranges;
2136     }
2137
2138     /**
2139      * Returns a path enclosing the visual selection in the specified range,
2140      * extended to {@code bounds}.
2141      * <p>
2142      * If the selection includes the leftmost (topmost) position, the selection
2143      * is extended to the left (top) of {@code bounds}.  If the
2144      * selection includes the rightmost (bottommost) position, the selection
2145      * is extended to the right (bottom) of the bounds.  The height
2146      * (width on vertical lines) of the selection is always extended to
2147      * {@code bounds}.
2148      * <p>
2149      * Although the selection is always contiguous, the logically selected
2150      * text can be discontiguous on lines with mixed-direction text.  The
2151      * logical ranges of text selected can be retrieved using
2152      * {@code getLogicalRangesForVisualSelection}.  For example,
2153      * consider the text 'ABCdef' where capital letters indicate
2154      * right-to-left text, rendered on a right-to-left line, with a visual
2155      * selection from 0L (the leading edge of 'A') to 3T (the trailing edge
2156      * of 'd').  The text appears as follows, with bold underlined areas
2157      * representing the selection:
2158      * <br><pre>
2159      *    d<u><b>efCBA  </b></u>
2160      * </pre>
2161      * The logical selection ranges are 0-3, 4-6 (ABC, ef) because the
2162      * visually contiguous text is logically discontiguous.  Also note that
2163      * since the rightmost position on the layout (to the right of 'A') is
2164      * selected, the selection is extended to the right of the bounds.
2165      * @param firstEndpoint one end of the visual selection
2166      * @param secondEndpoint the other end of the visual selection
2167      * @param bounds the bounding rectangle to which to extend the selection.
2168      *     This is in baseline-relative coordinates.
2169      * @return a {@code Shape} enclosing the selection.  This is in
2170      *     standard coordinates.
2171      * @see #getLogicalRangesForVisualSelection(TextHitInfo, TextHitInfo)
2172      * @see #getLogicalHighlightShape(intint, Rectangle2D)
2173      */

2174     public Shape getVisualHighlightShape(TextHitInfo firstEndpoint,
2175                                         TextHitInfo secondEndpoint,
2176                                         Rectangle2D bounds)
2177     {
2178         ensureCache();
2179
2180         checkTextHit(firstEndpoint);
2181         checkTextHit(secondEndpoint);
2182
2183         if(bounds == null) {
2184                 throw new IllegalArgumentException("Null Rectangle2D passed to TextLayout.getVisualHighlightShape()");
2185         }
2186
2187         GeneralPath result = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
2188
2189         int firstCaret = hitToCaret(firstEndpoint);
2190         int secondCaret = hitToCaret(secondEndpoint);
2191
2192         result.append(caretBoundingShape(firstCaret, secondCaret, bounds),
2193                       false);
2194
2195         if (firstCaret == 0 || secondCaret == 0) {
2196             GeneralPath ls = leftShape(bounds);
2197             if (!ls.getBounds().isEmpty())
2198                 result.append(ls, false);
2199         }
2200
2201         if (firstCaret == characterCount || secondCaret == characterCount) {
2202             GeneralPath rs = rightShape(bounds);
2203             if (!rs.getBounds().isEmpty()) {
2204                 result.append(rs, false);
2205             }
2206         }
2207
2208         LayoutPathImpl lp = textLine.getLayoutPath();
2209         if (lp != null) {
2210             result = (GeneralPath)lp.mapShape(result); // dlf cast safe?
2211         }
2212
2213         return  result;
2214     }
2215
2216     /**
2217      * Returns a {@code Shape} enclosing the visual selection in the
2218      * specified range, extended to the bounds.  This method is a
2219      * convenience overload of {@code getVisualHighlightShape} that
2220      * uses the natural bounds of this {@code TextLayout}.
2221      * @param firstEndpoint one end of the visual selection
2222      * @param secondEndpoint the other end of the visual selection
2223      * @return a {@code Shape} enclosing the selection.  This is
2224      *     in standard coordinates.
2225      */

2226     public Shape getVisualHighlightShape(TextHitInfo firstEndpoint,
2227                                              TextHitInfo secondEndpoint) {
2228         return getVisualHighlightShape(firstEndpoint, secondEndpoint, getNaturalBounds());
2229     }
2230
2231     /**
2232      * Returns a {@code Shape} enclosing the logical selection in the
2233      * specified range, extended to the specified {@code bounds}.
2234      * <p>
2235      * If the selection range includes the first logical character, the
2236      * selection is extended to the portion of {@code bounds} before
2237      * the start of this {@code TextLayout}.  If the range includes
2238      * the last logical character, the selection is extended to the portion
2239      * of {@code bounds} after the end of this {@code TextLayout}.
2240      * The height (width on vertical lines) of the selection is always
2241      * extended to {@code bounds}.
2242      * <p>
2243      * The selection can be discontiguous on lines with mixed-direction text.
2244      * Only those characters in the logical range between start and limit
2245      * appear selected.  For example, consider the text 'ABCdef' where capital
2246      * letters indicate right-to-left text, rendered on a right-to-left line,
2247      * with a logical selection from 0 to 4 ('ABCd').  The text appears as
2248      * follows, with bold standing in for the selection, and underlining for
2249      * the extension:
2250      * <br><pre>
2251      *    <u><b>d</b></u>ef<u><b>CBA  </b></u>
2252      * </pre>
2253      * The selection is discontiguous because the selected characters are
2254      * visually discontiguous. Also note that since the range includes the
2255      * first logical character (A), the selection is extended to the portion
2256      * of the {@code bounds} before the start of the layout, which in
2257      * this case (a right-to-left line) is the right portion of the
2258      * {@code bounds}.
2259      * @param firstEndpoint an endpoint in the range of characters to select
2260      * @param secondEndpoint the other endpoint of the range of characters
2261      * to select. Can be less than {@code firstEndpoint}.  The range
2262      * includes the character at min(firstEndpoint, secondEndpoint), but
2263      * excludes max(firstEndpoint, secondEndpoint).
2264      * @param bounds the bounding rectangle to which to extend the selection.
2265      *     This is in baseline-relative coordinates.
2266      * @return an area enclosing the selection.  This is in standard
2267      *     coordinates.
2268      * @see #getVisualHighlightShape(TextHitInfo, TextHitInfo, Rectangle2D)
2269      */

2270     public Shape getLogicalHighlightShape(int firstEndpoint,
2271                                          int secondEndpoint,
2272                                          Rectangle2D bounds) {
2273         if (bounds == null) {
2274             throw new IllegalArgumentException("Null Rectangle2D passed to TextLayout.getLogicalHighlightShape()");
2275         }
2276
2277         ensureCache();
2278
2279         if (firstEndpoint > secondEndpoint) {
2280             int t = firstEndpoint;
2281             firstEndpoint = secondEndpoint;
2282             secondEndpoint = t;
2283         }
2284
2285         if(firstEndpoint < 0 || secondEndpoint > characterCount) {
2286             throw new IllegalArgumentException("Range is invalid in TextLayout.getLogicalHighlightShape()");
2287         }
2288
2289         GeneralPath result = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
2290
2291         int[] carets = new int[10]; // would this ever not handle all cases?
2292         int count = 0;
2293
2294         if (firstEndpoint < secondEndpoint) {
2295             int logIndex = firstEndpoint;
2296             do {
2297                 carets[count++] = hitToCaret(TextHitInfo.leading(logIndex));
2298                 boolean ltr = textLine.isCharLTR(logIndex);
2299
2300                 do {
2301                     logIndex++;
2302                 } while (logIndex < secondEndpoint && textLine.isCharLTR(logIndex) == ltr);
2303
2304                 int hitCh = logIndex;
2305                 carets[count++] = hitToCaret(TextHitInfo.trailing(hitCh - 1));
2306
2307                 if (count == carets.length) {
2308                     int[] temp = new int[carets.length + 10];
2309                     System.arraycopy(carets, 0, temp, 0, count);
2310                     carets = temp;
2311                 }
2312             } while (logIndex < secondEndpoint);
2313         }
2314         else {
2315             count = 2;
2316             carets[0] = carets[1] = hitToCaret(TextHitInfo.leading(firstEndpoint));
2317         }
2318
2319         // now create paths for pairs of carets
2320
2321         for (int i = 0; i < count; i += 2) {
2322             result.append(caretBoundingShape(carets[i], carets[i+1], bounds),
2323                           false);
2324         }
2325
2326         if (firstEndpoint != secondEndpoint) {
2327             if ((textLine.isDirectionLTR() && firstEndpoint == 0) || (!textLine.isDirectionLTR() &&
2328                                                                       secondEndpoint == characterCount)) {
2329                 GeneralPath ls = leftShape(bounds);
2330                 if (!ls.getBounds().isEmpty()) {
2331                     result.append(ls, false);
2332                 }
2333             }
2334
2335             if ((textLine.isDirectionLTR() && secondEndpoint == characterCount) ||
2336                 (!textLine.isDirectionLTR() && firstEndpoint == 0)) {
2337
2338                 GeneralPath rs = rightShape(bounds);
2339                 if (!rs.getBounds().isEmpty()) {
2340                     result.append(rs, false);
2341                 }
2342             }
2343         }
2344
2345         LayoutPathImpl lp = textLine.getLayoutPath();
2346         if (lp != null) {
2347             result = (GeneralPath)lp.mapShape(result); // dlf cast safe?
2348         }
2349         return result;
2350     }
2351
2352     /**
2353      * Returns a {@code Shape} enclosing the logical selection in the
2354      * specified range, extended to the natural bounds of this
2355      * {@code TextLayout}.  This method is a convenience overload of
2356      * {@code getLogicalHighlightShape} that uses the natural bounds of
2357      * this {@code TextLayout}.
2358      * @param firstEndpoint an endpoint in the range of characters to select
2359      * @param secondEndpoint the other endpoint of the range of characters
2360      * to select. Can be less than {@code firstEndpoint}.  The range
2361      * includes the character at min(firstEndpoint, secondEndpoint), but
2362      * excludes max(firstEndpoint, secondEndpoint).
2363      * @return a {@code Shape} enclosing the selection.  This is in
2364      *     standard coordinates.
2365      */

2366     public Shape getLogicalHighlightShape(int firstEndpoint, int secondEndpoint) {
2367
2368         return getLogicalHighlightShape(firstEndpoint, secondEndpoint, getNaturalBounds());
2369     }
2370
2371     /**
2372      * Returns the black box bounds of the characters in the specified range.
2373      * The black box bounds is an area consisting of the union of the bounding
2374      * boxes of all the glyphs corresponding to the characters between start
2375      * and limit.  This area can be disjoint.
2376      * @param firstEndpoint one end of the character range
2377      * @param secondEndpoint the other end of the character range.  Can be
2378      * less than {@code firstEndpoint}.
2379      * @return a {@code Shape} enclosing the black box bounds.  This is
2380      *     in standard coordinates.
2381      */

2382     public Shape getBlackBoxBounds(int firstEndpoint, int secondEndpoint) {
2383         ensureCache();
2384
2385         if (firstEndpoint > secondEndpoint) {
2386             int t = firstEndpoint;
2387             firstEndpoint = secondEndpoint;
2388             secondEndpoint = t;
2389         }
2390
2391         if (firstEndpoint < 0 || secondEndpoint > characterCount) {
2392             throw new IllegalArgumentException("Invalid range passed to TextLayout.getBlackBoxBounds()");
2393         }
2394
2395         /*
2396          * return an area that consists of the bounding boxes of all the
2397          * characters from firstEndpoint to limit
2398          */

2399
2400         GeneralPath result = new GeneralPath(GeneralPath.WIND_NON_ZERO);
2401
2402         if (firstEndpoint < characterCount) {
2403             for (int logIndex = firstEndpoint;
2404                         logIndex < secondEndpoint;
2405                         logIndex++) {
2406
2407                 Rectangle2D r = textLine.getCharBounds(logIndex);
2408                 if (!r.isEmpty()) {
2409                     result.append(r, false);
2410                 }
2411             }
2412         }
2413
2414         if (dx != 0 || dy != 0) {
2415             AffineTransform tx = AffineTransform.getTranslateInstance(dx, dy);
2416             result = (GeneralPath)tx.createTransformedShape(result);
2417         }
2418         LayoutPathImpl lp = textLine.getLayoutPath();
2419         if (lp != null) {
2420             result = (GeneralPath)lp.mapShape(result);
2421         }
2422
2423         //return new Highlight(result, false);
2424         return result;
2425     }
2426
2427     /**
2428      * Returns the distance from the point (x,&nbsp;y) to the caret along
2429      * the line direction defined in {@code caretInfo}.  Distance is
2430      * negative if the point is to the left of the caret on a horizontal
2431      * line, or above the caret on a vertical line.
2432      * Utility for use by hitTestChar.
2433      */

2434     private float caretToPointDistance(float[] caretInfo, float x, float y) {
2435         // distanceOffBaseline is negative if you're 'above' baseline
2436
2437         float lineDistance = isVerticalLine? y : x;
2438         float distanceOffBaseline = isVerticalLine? -x : y;
2439
2440         return lineDistance - caretInfo[0] +
2441             (distanceOffBaseline*caretInfo[1]);
2442     }
2443
2444     /**
2445      * Returns a {@code TextHitInfo} corresponding to the
2446      * specified point.
2447      * Coordinates outside the bounds of the {@code TextLayout}
2448      * map to hits on the leading edge of the first logical character,
2449      * or the trailing edge of the last logical character, as appropriate,
2450      * regardless of the position of that character in the line.  Only the
2451      * direction along the baseline is used to make this evaluation.
2452      * @param x the x offset from the origin of this
2453      *     {@code TextLayout}.  This is in standard coordinates.
2454      * @param y the y offset from the origin of this
2455      *     {@code TextLayout}.  This is in standard coordinates.
2456      * @param bounds the bounds of the {@code TextLayout}.  This
2457      *     is in baseline-relative coordinates.
2458      * @return a hit describing the character and edge (leading or trailing)
2459      *     under the specified point.
2460      */

2461     public TextHitInfo hitTestChar(float x, float y, Rectangle2D bounds) {
2462         // check boundary conditions
2463
2464         LayoutPathImpl lp = textLine.getLayoutPath();
2465         boolean prev = false;
2466         if (lp != null) {
2467             Point2D.Float pt = new Point2D.Float(x, y);
2468             prev = lp.pointToPath(pt, pt);
2469             x = pt.x;
2470             y = pt.y;
2471         }
2472
2473         if (isVertical()) {
2474             if (y < bounds.getMinY()) {
2475                 return TextHitInfo.leading(0);
2476             } else if (y >= bounds.getMaxY()) {
2477                 return TextHitInfo.trailing(characterCount-1);
2478             }
2479         } else {
2480             if (x < bounds.getMinX()) {
2481                 return isLeftToRight() ? TextHitInfo.leading(0) : TextHitInfo.trailing(characterCount-1);
2482             } else if (x >= bounds.getMaxX()) {
2483                 return isLeftToRight() ? TextHitInfo.trailing(characterCount-1) : TextHitInfo.leading(0);
2484             }
2485         }
2486
2487         // revised hit test
2488         // the original seems too complex and fails miserably with italic offsets
2489         // the natural tendency is to move towards the character you want to hit
2490         // so we'll just measure distance to the center of each character's visual
2491         // bounds, pick the closest one, then see which side of the character's
2492         // center line (italic) the point is on.
2493         // this tends to make it easier to hit narrow characters, which can be a
2494         // bit odd if you're visually over an adjacent wide character. this makes
2495         // a difference with bidi, so perhaps i need to revisit this yet again.
2496
2497         double distance = Double.MAX_VALUE;
2498         int index = 0;
2499         int trail = -1;
2500         CoreMetrics lcm = null;
2501         float icx = 0, icy = 0, ia = 0, cy = 0, dya = 0, ydsq = 0;
2502
2503         for (int i = 0; i < characterCount; ++i) {
2504             if (!textLine.caretAtOffsetIsValid(i)) {
2505                 continue;
2506             }
2507             if (trail == -1) {
2508                 trail = i;
2509             }
2510             CoreMetrics cm = textLine.getCoreMetricsAt(i);
2511             if (cm != lcm) {
2512                 lcm = cm;
2513                 // just work around baseline mess for now
2514                 if (cm.baselineIndex == GraphicAttribute.TOP_ALIGNMENT) {
2515                     cy = -(textLine.getMetrics().ascent - cm.ascent) + cm.ssOffset;
2516                 } else if (cm.baselineIndex == GraphicAttribute.BOTTOM_ALIGNMENT) {
2517                     cy = textLine.getMetrics().descent - cm.descent + cm.ssOffset;
2518                 } else {
2519                     cy = cm.effectiveBaselineOffset(baselineOffsets) + cm.ssOffset;
2520                 }
2521                 float dy = (cm.descent - cm.ascent) / 2 - cy;
2522                 dya = dy * cm.italicAngle;
2523                 cy += dy;
2524                 ydsq = (cy - y)*(cy - y);
2525             }
2526             float cx = textLine.getCharXPosition(i);
2527             float ca = textLine.getCharAdvance(i);
2528             float dx = ca / 2;
2529             cx += dx - dya;
2530
2531             // proximity in x (along baseline) is two times as important as proximity in y
2532             double nd = Math.sqrt(4*(cx - x)*(cx - x) + ydsq);
2533             if (nd < distance) {
2534                 distance = nd;
2535                 index = i;
2536                 trail = -1;
2537                 icx = cx; icy = cy; ia = cm.italicAngle;
2538             }
2539         }
2540         boolean left = x < icx - (y - icy) * ia;
2541         boolean leading = textLine.isCharLTR(index) == left;
2542         if (trail == -1) {
2543             trail = characterCount;
2544         }
2545         TextHitInfo result = leading ? TextHitInfo.leading(index) :
2546             TextHitInfo.trailing(trail-1);
2547         return result;
2548     }
2549
2550     /**
2551      * Returns a {@code TextHitInfo} corresponding to the
2552      * specified point.  This method is a convenience overload of
2553      * {@code hitTestChar} that uses the natural bounds of this
2554      * {@code TextLayout}.
2555      * @param x the x offset from the origin of this
2556      *     {@code TextLayout}.  This is in standard coordinates.
2557      * @param y the y offset from the origin of this
2558      *     {@code TextLayout}.  This is in standard coordinates.
2559      * @return a hit describing the character and edge (leading or trailing)
2560      * under the specified point.
2561      */

2562     public TextHitInfo hitTestChar(float x, float y) {
2563
2564         return hitTestChar(x, y, getNaturalBounds());
2565     }
2566
2567     /**
2568      * Returns {@code trueif the two layouts are equal.
2569      * Obeys the general contract of {@link java.lang.Object equals(Object)}.
2570      * @param rhs the {@code TextLayout} to compare to this
2571      *       {@code TextLayout}
2572      * @return {@code trueif the specified {@code TextLayout}
2573      *      equals this {@code TextLayout}.
2574      *
2575      */

2576     public boolean equals(TextLayout rhs) {
2577         return equals((Object)rhs);
2578     }
2579
2580     /**
2581      * Returns debugging information for this {@code TextLayout}.
2582      * @return the {@code textLine} of this {@code TextLayout}
2583      *        as a {@code String}.
2584      */

2585     public String toString() {
2586         ensureCache();
2587         return textLine.toString();
2588      }
2589
2590     /**
2591      * Renders this {@code TextLayout} at the specified location in
2592      * the specified {@link java.awt.Graphics2D Graphics2D} context.
2593      * The origin of the layout is placed at x,&nbsp;y.  Rendering may touch
2594      * any point within {@code getBounds()} of this position.  This
2595      * leaves the {@code g2} unchanged.  Text is rendered along the
2596      * baseline path.
2597      * @param g2 the {@code Graphics2D} context into which to render
2598      *         the layout
2599      * @param x the X coordinate of the origin of this {@code TextLayout}
2600      * @param y the Y coordinate of the origin of this {@code TextLayout}
2601      * @see #getBounds()
2602      */

2603     public void draw(Graphics2D g2, float x, float y) {
2604
2605         if (g2 == null) {
2606             throw new IllegalArgumentException("Null Graphics2D passed to TextLayout.draw()");
2607         }
2608
2609         textLine.draw(g2, x - dx, y - dy);
2610     }
2611
2612     /**
2613      * Package-only method for testing ONLY.  Please don't abuse.
2614      */

2615     TextLine getTextLineForTesting() {
2616
2617         return textLine;
2618     }
2619
2620     /**
2621      *
2622      * Return the index of the first character with a different baseline from the
2623      * character at start, or limit if all characters between start and limit have
2624      * the same baseline.
2625      */

2626     private static int sameBaselineUpTo(Font font, char[] text,
2627                                         int start, int limit) {
2628         // current implementation doesn't support multiple baselines
2629         return limit;
2630         /*
2631         byte bl = font.getBaselineFor(text[start++]);
2632         while (start < limit && font.getBaselineFor(text[start]) == bl) {
2633             ++start;
2634         }
2635         return start;
2636         */

2637     }
2638
2639     static byte getBaselineFromGraphic(GraphicAttribute graphic) {
2640
2641         byte alignment = (byte) graphic.getAlignment();
2642
2643         if (alignment == GraphicAttribute.BOTTOM_ALIGNMENT ||
2644                 alignment == GraphicAttribute.TOP_ALIGNMENT) {
2645
2646             return (byte)GraphicAttribute.ROMAN_BASELINE;
2647         }
2648         else {
2649             return alignment;
2650         }
2651     }
2652
2653     /**
2654      * Returns a {@code Shape} representing the outline of this
2655      * {@code TextLayout}.
2656      * @param tx an optional {@link AffineTransform} to apply to the
2657      *     outline of this {@code TextLayout}.
2658      * @return a {@code Shape} that is the outline of this
2659      *     {@code TextLayout}.  This is in standard coordinates.
2660      */

2661     public Shape getOutline(AffineTransform tx) {
2662         ensureCache();
2663         Shape result = textLine.getOutline(tx);
2664         LayoutPathImpl lp = textLine.getLayoutPath();
2665         if (lp != null) {
2666             result = lp.mapShape(result);
2667         }
2668         return result;
2669     }
2670
2671     /**
2672      * Return the LayoutPath, or null if the layout path is the
2673      * default path (x maps to advance, y maps to offset).
2674      * @return the layout path
2675      * @since 1.6
2676      */

2677     public LayoutPath getLayoutPath() {
2678         return textLine.getLayoutPath();
2679     }
2680
2681    /**
2682      * Convert a hit to a point in standard coordinates.  The point is
2683      * on the baseline of the character at the leading or trailing
2684      * edge of the character, as appropriate.  If the path is
2685      * broken at the side of the character represented by the hit, the
2686      * point will be adjacent to the character.
2687      * @param hit the hit to check.  This must be a valid hit on
2688      * the TextLayout.
2689      * @param point the returned point. The point is in standard
2690      *     coordinates.
2691      * @throws IllegalArgumentException if the hit is not valid for the
2692      * TextLayout.
2693      * @throws NullPointerException if hit or point is null.
2694      * @since 1.6
2695      */

2696     public void hitToPoint(TextHitInfo hit, Point2D point) {
2697         if (hit == null || point == null) {
2698             throw new NullPointerException((hit == null ? "hit" : "point") +
2699                                            " can't be null");
2700         }
2701         ensureCache();
2702         checkTextHit(hit);
2703
2704         float adv = 0;
2705         float off = 0;
2706
2707         int ix = hit.getCharIndex();
2708         boolean leading = hit.isLeadingEdge();
2709         boolean ltr;
2710         if (ix == -1 || ix == textLine.characterCount()) {
2711             ltr = textLine.isDirectionLTR();
2712             adv = (ltr == (ix == -1)) ? 0 : lineMetrics.advance;
2713         } else {
2714             ltr = textLine.isCharLTR(ix);
2715             adv = textLine.getCharLinePosition(ix, leading);
2716             off = textLine.getCharYPosition(ix);
2717         }
2718         point.setLocation(adv, off);
2719         LayoutPath lp = textLine.getLayoutPath();
2720         if (lp != null) {
2721             lp.pathToPoint(point, ltr != leading, point);
2722         }
2723     }
2724 }
2725