Source Code Cross Referenced for TextLayout.java in  » 6.0-JDK-Core » AWT » java » awt » font » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Home
Java Source Code / Java Documentation
1.6.0 JDK Core
2.6.0 JDK Modules
3.6.0 JDK Modules com.sun
4.6.0 JDK Modules com.sun.java
5.6.0 JDK Modules sun
6.6.0 JDK Platform
7.Ajax
8.Apache Harmony Java SE
9.Aspect oriented
10.Authentication Authorization
11.Blogger System
12.Build
13.Byte Code
14.Cache
15.Chart
16.Chat
17.Code Analyzer
18.Collaboration
19.Content Management System
20.Database Client
21.Database DBMS
22.Database JDBC Connection Pool
23.Database ORM
24.Development
25.EJB Server
26.ERP CRM Financial
27.ESB
28.Forum
29.Game
30.GIS
31.Graphic 3D
32.Graphic Library
33.Groupware
34.HTML Parser
35.IDE
36.IDE Eclipse
37.IDE Netbeans
38.Installer
39.Internationalization Localization
40.Inversion of Control
41.Issue Tracking
42.J2EE
43.J2ME
44.JBoss
45.JMS
46.JMX
47.Library
48.Mail Clients
49.Music
50.Net
51.Parser
52.PDF
53.Portal
54.Profiler
55.Project Management
56.Report
57.RSS RDF
58.Rule Engine
59.Science
60.Scripting
61.Search Engine
62.Security
63.Sevlet Container
64.Source Control
65.Swing Library
66.Template Engine
67.Test Coverage
68.Testing
69.UML
70.Web Crawler
71.Web Framework
72.Web Mail
73.Web Server
74.Web Services
75.Web Services apache cxf 2.2.6
76.Web Services AXIS2
77.Wiki Engine
78.Workflow Engines
79.XML
80.XML UI
Java Source Code / Java Documentation » 6.0 JDK Core » AWT » java.awt.font 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001        /*
0002         * Copyright 1997-2006 Sun Microsystems, Inc.  All Rights Reserved.
0003         * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004         *
0005         * This code is free software; you can redistribute it and/or modify it
0006         * under the terms of the GNU General Public License version 2 only, as
0007         * published by the Free Software Foundation.  Sun designates this
0008         * particular file as subject to the "Classpath" exception as provided
0009         * by Sun in the LICENSE file that accompanied this code.
0010         *
0011         * This code is distributed in the hope that it will be useful, but WITHOUT
0012         * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013         * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
0014         * version 2 for more details (a copy is included in the LICENSE file that
0015         * accompanied this code).
0016         *
0017         * You should have received a copy of the GNU General Public License version
0018         * 2 along with this work; if not, write to the Free Software Foundation,
0019         * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020         *
0021         * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022         * CA 95054 USA or visit www.sun.com if you need additional information or
0023         * have any questions.
0024         */
0025
0026        /*
0027         * (C) Copyright Taligent, Inc. 1996 - 1997, All Rights Reserved
0028         * (C) Copyright IBM Corp. 1996-2003, All Rights Reserved
0029         *
0030         * The original version of this source code and documentation is
0031         * copyrighted and owned by Taligent, Inc., a wholly-owned subsidiary
0032         * of IBM. These materials are provided under terms of a License
0033         * Agreement between Taligent and Sun. This technology is protected
0034         * by multiple US and International patents.
0035         *
0036         * This notice and attribution to Taligent may not be removed.
0037         * Taligent is a registered trademark of Taligent, Inc.
0038         *
0039         */
0040
0041        package java.awt.font;
0042
0043        import java.awt.Color;
0044        import java.awt.Font;
0045        import java.awt.Graphics2D;
0046        import java.awt.Rectangle;
0047        import java.awt.Shape;
0048        import java.awt.font.NumericShaper;
0049        import java.awt.font.TextLine.TextLineMetrics;
0050        import java.awt.geom.AffineTransform;
0051        import java.awt.geom.GeneralPath;
0052        import java.awt.geom.NoninvertibleTransformException;
0053        import java.awt.geom.Point2D;
0054        import java.awt.geom.Rectangle2D;
0055        import java.text.AttributedString;
0056        import java.text.AttributedCharacterIterator;
0057        import java.text.AttributedCharacterIterator.Attribute;
0058        import java.util.Map;
0059        import java.util.HashMap;
0060        import java.util.Hashtable;
0061        import sun.font.AttributeValues;
0062        import sun.font.CoreMetrics;
0063        import sun.font.Decoration;
0064        import sun.font.FontLineMetrics;
0065        import sun.font.FontResolver;
0066        import sun.font.GraphicComponent;
0067        import sun.font.LayoutPathImpl;
0068        import sun.text.CodePointIterator;
0069
0070        /**
0071         * 
0072         * <code>TextLayout</code> is an immutable graphical representation of styled 
0073         * character data.
0074         * <p>
0075         * It provides the following capabilities:
0076         * <ul>
0077         * <li>implicit bidirectional analysis and reordering,
0078         * <li>cursor positioning and movement, including split cursors for
0079         * mixed directional text,
0080         * <li>highlighting, including both logical and visual highlighting
0081         * for mixed directional text,
0082         * <li>multiple baselines (roman, hanging, and centered),
0083         * <li>hit testing,
0084         * <li>justification,
0085         * <li>default font substitution,
0086         * <li>metric information such as ascent, descent, and advance, and
0087         * <li>rendering
0088         * </ul>
0089         * <p>
0090         * A <code>TextLayout</code> object can be rendered using 
0091         * its <code>draw</code> method.
0092         * <p>
0093         * <code>TextLayout</code> can be constructed either directly or through 
0094         * the use of a {@link LineBreakMeasurer}.  When constructed directly, the 
0095         * source text represents a single paragraph.  <code>LineBreakMeasurer</code>
0096         * allows styled text to be broken into lines that fit within a particular 
0097         * width.  See the <code>LineBreakMeasurer</code> documentation for more
0098         * information.
0099         * <p>
0100         * <code>TextLayout</code> construction logically proceeds as follows:
0101         * <ul>
0102         * <li>paragraph attributes are extracted and examined,
0103         * <li>text is analyzed for bidirectional reordering, and reordering
0104         * information is computed if needed,
0105         * <li>text is segmented into style runs
0106         * <li>fonts are chosen for style runs, first by using a font if the
0107         * attribute {@link TextAttribute#FONT} is present, otherwise by computing
0108         * a default font using the attributes that have been defined
0109         * <li>if text is on multiple baselines, the runs or subruns are further
0110         * broken into subruns sharing a common baseline,
0111         * <li>glyphvectors are generated for each run using the chosen font,
0112         * <li>final bidirectional reordering is performed on the glyphvectors
0113         * </ul>
0114         * <p>
0115         * All graphical information returned from a <code>TextLayout</code> 
0116         * object's methods is relative to the origin of the 
0117         * <code>TextLayout</code>, which is the intersection of the
0118         * <code>TextLayout</code> object's baseline with its left edge.  Also, 
0119         * coordinates passed into a <code>TextLayout</code> object's methods 
0120         * are assumed to be relative to the <code>TextLayout</code> object's
0121         * origin.  Clients usually need to translate between a 
0122         * <code>TextLayout</code> object's coordinate system and the coordinate
0123         * system in another object (such as a 
0124         * {@link java.awt.Graphics Graphics} object).
0125         * <p>
0126         * <code>TextLayout</code> objects are constructed from styled text, 
0127         * but they do not retain a reference to their source text.  Thus, 
0128         * changes in the text previously used to generate a <code>TextLayout</code>
0129         * do not affect the <code>TextLayout</code>.
0130         * <p>
0131         * Three methods on a <code>TextLayout</code> object 
0132         * (<code>getNextRightHit</code>, <code>getNextLeftHit</code>, and 
0133         * <code>hitTestChar</code>) return instances of {@link TextHitInfo}.  
0134         * The offsets contained in these <code>TextHitInfo</code> objects
0135         * are relative to the start of the <code>TextLayout</code>, <b>not</b> 
0136         * to the text used to create the <code>TextLayout</code>.  Similarly, 
0137         * <code>TextLayout</code> methods that accept <code>TextHitInfo</code>
0138         * instances as parameters expect the <code>TextHitInfo</code> object's 
0139         * offsets to be relative to the <code>TextLayout</code>, not to any 
0140         * underlying text storage model.
0141         * <p>
0142         * <strong>Examples</strong>:<p>
0143         * Constructing and drawing a <code>TextLayout</code> and its bounding 
0144         * rectangle:
0145         * <blockquote><pre>
0146         *   Graphics2D g = ...;
0147         *   Point2D loc = ...;
0148         *   Font font = Font.getFont("Helvetica-bold-italic");
0149         *   FontRenderContext frc = g.getFontRenderContext();
0150         *   TextLayout layout = new TextLayout("This is a string", font, frc);
0151         *   layout.draw(g, (float)loc.getX(), (float)loc.getY());
0152         *
0153         *   Rectangle2D bounds = layout.getBounds();
0154         *   bounds.setRect(bounds.getX()+loc.getX(),
0155         *                  bounds.getY()+loc.getY(),
0156         *                  bounds.getWidth(),
0157         *                  bounds.getHeight());
0158         *   g.draw(bounds);
0159         * </pre>
0160         * </blockquote>
0161         * <p>
0162         * Hit-testing a <code>TextLayout</code> (determining which character is at
0163         * a particular graphical location):
0164         * <blockquote><pre>
0165         *   Point2D click = ...;
0166         *   TextHitInfo hit = layout.hitTestChar(
0167         *                         (float) (click.getX() - loc.getX()),
0168         *                         (float) (click.getY() - loc.getY()));
0169         * </pre>
0170         * </blockquote>
0171         * <p>
0172         * Responding to a right-arrow key press:
0173         * <blockquote><pre>
0174         *   int insertionIndex = ...;
0175         *   TextHitInfo next = layout.getNextRightHit(insertionIndex);
0176         *   if (next != null) {
0177         *       // translate graphics to origin of layout on screen
0178         *       g.translate(loc.getX(), loc.getY());
0179         *       Shape[] carets = layout.getCaretShapes(next.getInsertionIndex());
0180         *       g.draw(carets[0]);
0181         *       if (carets[1] != null) {
0182         *           g.draw(carets[1]);
0183         *       }
0184         *   }
0185         * </pre></blockquote>
0186         * <p>
0187         * Drawing a selection range corresponding to a substring in the source text.
0188         * The selected area may not be visually contiguous:
0189         * <blockquote><pre>
0190         *   // selStart, selLimit should be relative to the layout,
0191         *   // not to the source text
0192         *
0193         *   int selStart = ..., selLimit = ...;
0194         *   Color selectionColor = ...;
0195         *   Shape selection = layout.getLogicalHighlightShape(selStart, selLimit);
0196         *   // selection may consist of disjoint areas
0197         *   // graphics is assumed to be tranlated to origin of layout
0198         *   g.setColor(selectionColor);
0199         *   g.fill(selection);
0200         * </pre></blockquote>
0201         * <p>
0202         * Drawing a visually contiguous selection range.  The selection range may
0203         * correspond to more than one substring in the source text.  The ranges of
0204         * the corresponding source text substrings can be obtained with
0205         * <code>getLogicalRangesForVisualSelection()</code>:
0206         * <blockquote><pre>
0207         *   TextHitInfo selStart = ..., selLimit = ...;
0208         *   Shape selection = layout.getVisualHighlightShape(selStart, selLimit);
0209         *   g.setColor(selectionColor);
0210         *   g.fill(selection);
0211         *   int[] ranges = getLogicalRangesForVisualSelection(selStart, selLimit);
0212         *   // ranges[0], ranges[1] is the first selection range,
0213         *   // ranges[2], ranges[3] is the second selection range, etc.
0214         * </pre></blockquote>
0215         * <p>
0216         * Note: Font rotations can cause text baselines to be rotated, and
0217         * multiple runs with different rotations can cause the baseline to
0218         * bend or zig-zag.  In order to account for this (rare) possibility,
0219         * some APIs are specified to return metrics and take parameters 'in
0220         * baseline-relative coordinates' (e.g. ascent, advance), and others
0221         * are in 'in standard coordinates' (e.g. getBounds).  Values in
0222         * baseline-relative coordinates map the 'x' coordinate to the
0223         * distance along the baseline, (positive x is forward along the
0224         * baseline), and the 'y' coordinate to a distance along the
0225         * perpendicular to the baseline at 'x' (postitive y is 90 degrees
0226         * clockwise from the baseline vector).  Values in standard
0227         * coordinates are measured along the x and y axes, with 0,0 at the
0228         * origin of the TextLayout.  Documentation for each relevant API
0229         * indicates what values are in what coordinate system.  In general,
0230         * measurement-related APIs are in baseline-relative coordinates,
0231         * while display-related APIs are in standard coordinates.
0232         *
0233         * @see LineBreakMeasurer
0234         * @see TextAttribute
0235         * @see TextHitInfo
0236         * @see LayoutPath
0237         */
0238        public final class TextLayout implements  Cloneable {
0239
0240            private int characterCount;
0241            private boolean isVerticalLine = false;
0242            private byte baseline;
0243            private float[] baselineOffsets; // why have these ?
0244            private TextLine textLine;
0245
0246            // cached values computed from GlyphSets and set info:
0247            // all are recomputed from scratch in buildCache()
0248            private TextLine.TextLineMetrics lineMetrics = null;
0249            private float visibleAdvance;
0250            private int hashCodeCache;
0251
0252            /*
0253             * TextLayouts are supposedly immutable.  If you mutate a TextLayout under
0254             * the covers (like the justification code does) you'll need to set this
0255             * back to false.  Could be replaced with textLine != null <--> cacheIsValid.
0256             */
0257            private boolean cacheIsValid = false;
0258
0259            // This value is obtained from an attribute, and constrained to the
0260            // interval [0,1].  If 0, the layout cannot be justified.
0261            private float justifyRatio;
0262
0263            // If a layout is produced by justification, then that layout
0264            // cannot be justified.  To enforce this constraint the
0265            // justifyRatio of the justified layout is set to this value.
0266            private static final float ALREADY_JUSTIFIED = -53.9f;
0267
0268            // dx and dy specify the distance between the TextLayout's origin
0269            // and the origin of the leftmost GlyphSet (TextLayoutComponent,
0270            // actually).  They were used for hanging punctuation support,
0271            // which is no longer implemented.  Currently they are both always 0,
0272            // and TextLayout is not guaranteed to work with non-zero dx, dy
0273            // values right now.  They were left in as an aide and reminder to
0274            // anyone who implements hanging punctuation or other similar stuff.
0275            // They are static now so they don't take up space in TextLayout
0276            // instances.
0277            private static float dx;
0278            private static float dy;
0279
0280            /*
0281             * Natural bounds is used internally.  It is built on demand in
0282             * getNaturalBounds.
0283             */
0284            private Rectangle2D naturalBounds = null;
0285
0286            /*
0287             * boundsRect encloses all of the bits this TextLayout can draw.  It
0288             * is build on demand in getBounds.
0289             */
0290            private Rectangle2D boundsRect = null;
0291
0292            /*
0293             * flag to supress/allow carets inside of ligatures when hit testing or
0294             * arrow-keying
0295             */
0296            private boolean caretsInLigaturesAreAllowed = false;
0297
0298            /**
0299             * Defines a policy for determining the strong caret location.
0300             * This class contains one method, <code>getStrongCaret</code>, which
0301             * is used to specify the policy that determines the strong caret in 
0302             * dual-caret text.  The strong caret is used to move the caret to the
0303             * left or right. Instances of this class can be passed to
0304             * <code>getCaretShapes</code>, <code>getNextLeftHit</code> and 
0305             * <code>getNextRightHit</code> to customize strong caret
0306             * selection.
0307             * <p>
0308             * To specify alternate caret policies, subclass <code>CaretPolicy</code>
0309             * and override <code>getStrongCaret</code>.  <code>getStrongCaret</code>
0310             * should inspect the two <code>TextHitInfo</code> arguments and choose
0311             * one of them as the strong caret.
0312             * <p>
0313             * Most clients do not need to use this class.
0314             */
0315            public static class CaretPolicy {
0316
0317                /**
0318                 * Constructs a <code>CaretPolicy</code>.
0319                 */
0320                public CaretPolicy() {
0321                }
0322
0323                /**
0324                 * Chooses one of the specified <code>TextHitInfo</code> instances as 
0325                 * a strong caret in the specified <code>TextLayout</code>.
0326                 * @param hit1 a valid hit in <code>layout</code>
0327                 * @param hit2 a valid hit in <code>layout</code>
0328                 * @param layout the <code>TextLayout</code> in which 
0329                 *        <code>hit1</code> and <code>hit2</code> are used
0330                 * @return <code>hit1</code> or <code>hit2</code>
0331                 *        (or an equivalent <code>TextHitInfo</code>), indicating the
0332                 *        strong caret.
0333                 */
0334                public TextHitInfo getStrongCaret(TextHitInfo hit1,
0335                        TextHitInfo hit2, TextLayout layout) {
0336
0337                    // default implmentation just calls private method on layout
0338                    return layout.getStrongHit(hit1, hit2);
0339                }
0340            }
0341
0342            /**
0343             * This <code>CaretPolicy</code> is used when a policy is not specified 
0344             * by the client.  With this policy, a hit on a character whose direction
0345             * is the same as the line direction is stronger than a hit on a
0346             * counterdirectional character.  If the characters' directions are
0347             * the same, a hit on the leading edge of a character is stronger
0348             * than a hit on the trailing edge of a character.
0349             */
0350            public static final CaretPolicy DEFAULT_CARET_POLICY = new CaretPolicy();
0351
0352            /**
0353             * Constructs a <code>TextLayout</code> from a <code>String</code>
0354             * and a {@link Font}.  All the text is styled using the specified
0355             * <code>Font</code>.
0356             * <p>
0357             * The <code>String</code> must specify a single paragraph of text, 
0358             * because an entire paragraph is required for the bidirectional
0359             * algorithm.
0360             * @param string the text to display
0361             * @param font a <code>Font</code> used to style the text
0362             * @param frc contains information about a graphics device which is needed 
0363             *       to measure the text correctly.
0364             *       Text measurements can vary slightly depending on the
0365             *       device resolution, and attributes such as antialiasing.  This
0366             *       parameter does not specify a translation between the
0367             *       <code>TextLayout</code> and user space.
0368             */
0369            public TextLayout(String string, Font font, FontRenderContext frc) {
0370
0371                if (font == null) {
0372                    throw new IllegalArgumentException(
0373                            "Null font passed to TextLayout constructor.");
0374                }
0375
0376                if (string == null) {
0377                    throw new IllegalArgumentException(
0378                            "Null string passed to TextLayout constructor.");
0379                }
0380
0381                if (string.length() == 0) {
0382                    throw new IllegalArgumentException(
0383                            "Zero length string passed to TextLayout constructor.");
0384                }
0385
0386                Map attributes = null;
0387                if (font.hasLayoutAttributes()) {
0388                    attributes = font.getAttributes();
0389                }
0390
0391                char[] text = string.toCharArray();
0392                if (sameBaselineUpTo(font, text, 0, text.length) == text.length) {
0393                    fastInit(text, font, attributes, frc);
0394                } else {
0395                    AttributedString as = attributes == null ? new AttributedString(
0396                            string)
0397                            : new AttributedString(string, attributes);
0398                    as.addAttribute(TextAttribute.FONT, font);
0399                    standardInit(as.getIterator(), text, frc);
0400                }
0401            }
0402
0403            /**
0404             * Constructs a <code>TextLayout</code> from a <code>String</code>
0405             * and an attribute set.
0406             * <p>
0407             * All the text is styled using the provided attributes.
0408             * <p>
0409             * <code>string</code> must specify a single paragraph of text because an
0410             * entire paragraph is required for the bidirectional algorithm.
0411             * @param string the text to display
0412             * @param attributes the attributes used to style the text
0413             * @param frc contains information about a graphics device which is needed 
0414             *       to measure the text correctly.
0415             *       Text measurements can vary slightly depending on the
0416             *       device resolution, and attributes such as antialiasing.  This
0417             *       parameter does not specify a translation between the
0418             *       <code>TextLayout</code> and user space.
0419             */
0420            public TextLayout(String string,
0421                    Map<? extends Attribute, ?> attributes,
0422                    FontRenderContext frc) {
0423                if (string == null) {
0424                    throw new IllegalArgumentException(
0425                            "Null string passed to TextLayout constructor.");
0426                }
0427
0428                if (attributes == null) {
0429                    throw new IllegalArgumentException(
0430                            "Null map passed to TextLayout constructor.");
0431                }
0432
0433                if (string.length() == 0) {
0434                    throw new IllegalArgumentException(
0435                            "Zero length string passed to TextLayout constructor.");
0436                }
0437
0438                char[] text = string.toCharArray();
0439                Font font = singleFont(text, 0, text.length, attributes);
0440                if (font != null) {
0441                    fastInit(text, font, attributes, frc);
0442                } else {
0443                    AttributedString as = new AttributedString(string,
0444                            attributes);
0445                    standardInit(as.getIterator(), text, frc);
0446                }
0447            }
0448
0449            /*
0450             * Determines a font for the attributes, and if a single font can render
0451             * all the text on one baseline, return it, otherwise null.  If the 
0452             * attributes specify a font, assume it can display all the text without 
0453             * checking.
0454             * If the AttributeSet contains an embedded graphic, return null.
0455             */
0456            private static Font singleFont(char[] text, int start, int limit,
0457                    Map attributes) {
0458
0459                if (attributes.get(TextAttribute.CHAR_REPLACEMENT) != null) {
0460                    return null;
0461                }
0462
0463                Font font = null;
0464                try {
0465                    font = (Font) attributes.get(TextAttribute.FONT);
0466                } catch (ClassCastException e) {
0467                }
0468                if (font == null) {
0469                    if (attributes.get(TextAttribute.FAMILY) != null) {
0470                        font = Font.getFont(attributes);
0471                        if (font.canDisplayUpTo(text, start, limit) != -1) {
0472                            return null;
0473                        }
0474                    } else {
0475                        FontResolver resolver = FontResolver.getInstance();
0476                        CodePointIterator iter = CodePointIterator.create(text,
0477                                start, limit);
0478                        int fontIndex = resolver.nextFontRunIndex(iter);
0479                        if (iter.charIndex() == limit) {
0480                            font = resolver.getFont(fontIndex, attributes);
0481                        }
0482                    }
0483                }
0484
0485                if (sameBaselineUpTo(font, text, start, limit) != limit) {
0486                    return null;
0487                }
0488
0489                return font;
0490            }
0491
0492            /**
0493             * Constructs a <code>TextLayout</code> from an iterator over styled text.
0494             * <p>
0495             * The iterator must specify a single paragraph of text because an
0496             * entire paragraph is required for the bidirectional
0497             * algorithm.
0498             * @param text the styled text to display
0499             * @param frc contains information about a graphics device which is needed 
0500             *       to measure the text correctly.
0501             *       Text measurements can vary slightly depending on the
0502             *       device resolution, and attributes such as antialiasing.  This
0503             *       parameter does not specify a translation between the
0504             *       <code>TextLayout</code> and user space.
0505             */
0506            public TextLayout(AttributedCharacterIterator text,
0507                    FontRenderContext frc) {
0508
0509                if (text == null) {
0510                    throw new IllegalArgumentException(
0511                            "Null iterator passed to TextLayout constructor.");
0512                }
0513
0514                int start = text.getBeginIndex();
0515                int limit = text.getEndIndex();
0516                if (start == limit) {
0517                    throw new IllegalArgumentException(
0518                            "Zero length iterator passed to TextLayout constructor.");
0519                }
0520
0521                int len = limit - start;
0522                text.first();
0523                char[] chars = new char[len];
0524                int n = 0;
0525                for (char c = text.first(); c != text.DONE; c = text.next()) {
0526                    chars[n++] = c;
0527                }
0528
0529                text.first();
0530                if (text.getRunLimit() == limit) {
0531
0532                    Map attributes = text.getAttributes();
0533                    Font font = singleFont(chars, 0, len, attributes);
0534                    if (font != null) {
0535                        fastInit(chars, font, attributes, frc);
0536                        return;
0537                    }
0538                }
0539
0540                standardInit(text, chars, frc);
0541            }
0542
0543            /**
0544             * Creates a <code>TextLayout</code> from a {@link TextLine} and 
0545             * some paragraph data.  This method is used by {@link TextMeasurer}.
0546             * @param textLine the line measurement attributes to apply to the
0547             *       the resulting <code>TextLayout</code>
0548             * @param baseline the baseline of the text
0549             * @param baselineOffsets the baseline offsets for this 
0550             * <code>TextLayout</code>.  This should already be normalized to 
0551             * <code>baseline</code>
0552             * @param justifyRatio <code>0</code> if the <code>TextLayout</code>
0553             *     cannot be justified; <code>1</code> otherwise.
0554             */
0555            TextLayout(TextLine textLine, byte baseline,
0556                    float[] baselineOffsets, float justifyRatio) {
0557
0558                this .characterCount = textLine.characterCount();
0559                this .baseline = baseline;
0560                this .baselineOffsets = baselineOffsets;
0561                this .textLine = textLine;
0562                this .justifyRatio = justifyRatio;
0563            }
0564
0565            /**
0566             * Initialize the paragraph-specific data.
0567             */
0568            private void paragraphInit(byte aBaseline, CoreMetrics lm,
0569                    Map paragraphAttrs, char[] text) {
0570
0571                baseline = aBaseline;
0572
0573                // normalize to current baseline
0574                baselineOffsets = TextLine.getNormalizedOffsets(
0575                        lm.baselineOffsets, baseline);
0576
0577                justifyRatio = AttributeValues.getJustification(paragraphAttrs);
0578                NumericShaper shaper = AttributeValues
0579                        .getNumericShaping(paragraphAttrs);
0580                if (shaper != null) {
0581                    shaper.shape(text, 0, text.length);
0582                }
0583            }
0584
0585            /*
0586             * the fast init generates a single glyph set.  This requires:
0587             * all one style
0588             * all renderable by one font (ie no embedded graphics)
0589             * all on one baseline
0590             */
0591            private void fastInit(char[] chars, Font font, Map attrs,
0592                    FontRenderContext frc) {
0593                // Object vf = attrs.get(TextAttribute.ORIENTATION);
0594                // isVerticalLine = TextAttribute.ORIENTATION_VERTICAL.equals(vf);
0595                isVerticalLine = false;
0596
0597                LineMetrics lm = font.getLineMetrics(chars, 0, chars.length,
0598                        frc);
0599                CoreMetrics cm = CoreMetrics.get(lm);
0600                byte glyphBaseline = (byte) cm.baselineIndex;
0601
0602                if (attrs == null) {
0603                    baseline = glyphBaseline;
0604                    baselineOffsets = cm.baselineOffsets;
0605                    justifyRatio = 1.0f;
0606                } else {
0607                    paragraphInit(glyphBaseline, cm, attrs, chars);
0608                }
0609
0610                characterCount = chars.length;
0611
0612                textLine = TextLine.fastCreateTextLine(frc, chars, font, cm,
0613                        attrs);
0614            }
0615
0616            /*
0617             * the standard init generates multiple glyph sets based on style,
0618             * renderable, and baseline runs.
0619             * @param chars the text in the iterator, extracted into a char array
0620             */
0621            private void standardInit(AttributedCharacterIterator text,
0622                    char[] chars, FontRenderContext frc) {
0623
0624                characterCount = chars.length;
0625
0626                // set paragraph attributes
0627                {
0628                    // If there's an embedded graphic at the start of the
0629                    // paragraph, look for the first non-graphic character
0630                    // and use it and its font to initialize the paragraph.
0631                    // If not, use the first graphic to initialize.
0632
0633                    Map paragraphAttrs = text.getAttributes();
0634
0635                    boolean haveFont = TextLine.advanceToFirstFont(text);
0636
0637                    if (haveFont) {
0638                        Font defaultFont = TextLine.getFontAtCurrentPos(text);
0639                        int charsStart = text.getIndex() - text.getBeginIndex();
0640                        LineMetrics lm = defaultFont.getLineMetrics(chars,
0641                                charsStart, charsStart + 1, frc);
0642                        CoreMetrics cm = CoreMetrics.get(lm);
0643                        paragraphInit((byte) cm.baselineIndex, cm,
0644                                paragraphAttrs, chars);
0645                    } else {
0646                        // hmmm what to do here?  Just try to supply reasonable
0647                        // values I guess.
0648
0649                        GraphicAttribute graphic = (GraphicAttribute) paragraphAttrs
0650                                .get(TextAttribute.CHAR_REPLACEMENT);
0651                        byte defaultBaseline = getBaselineFromGraphic(graphic);
0652                        CoreMetrics cm = GraphicComponent
0653                                .createCoreMetrics(graphic);
0654                        paragraphInit(defaultBaseline, cm, paragraphAttrs,
0655                                chars);
0656                    }
0657                }
0658
0659                textLine = TextLine.standardCreateTextLine(frc, text, chars,
0660                        baselineOffsets);
0661            }
0662
0663            /*
0664             * A utility to rebuild the ascent/descent/leading/advance cache.
0665             * You'll need to call this if you clone and mutate (like justification,
0666             * editing methods do)
0667             */
0668            private void ensureCache() {
0669                if (!cacheIsValid) {
0670                    buildCache();
0671                }
0672            }
0673
0674            private void buildCache() {
0675                lineMetrics = textLine.getMetrics();
0676
0677                // compute visibleAdvance
0678                if (textLine.isDirectionLTR()) {
0679
0680                    int lastNonSpace = characterCount - 1;
0681                    while (lastNonSpace != -1) {
0682                        int logIndex = textLine.visualToLogical(lastNonSpace);
0683                        if (!textLine.isCharSpace(logIndex)) {
0684                            break;
0685                        } else {
0686                            --lastNonSpace;
0687                        }
0688                    }
0689                    if (lastNonSpace == characterCount - 1) {
0690                        visibleAdvance = lineMetrics.advance;
0691                    } else if (lastNonSpace == -1) {
0692                        visibleAdvance = 0;
0693                    } else {
0694                        int logIndex = textLine.visualToLogical(lastNonSpace);
0695                        visibleAdvance = textLine.getCharLinePosition(logIndex)
0696                                + textLine.getCharAdvance(logIndex);
0697                    }
0698                } else {
0699
0700                    int leftmostNonSpace = 0;
0701                    while (leftmostNonSpace != characterCount) {
0702                        int logIndex = textLine
0703                                .visualToLogical(leftmostNonSpace);
0704                        if (!textLine.isCharSpace(logIndex)) {
0705                            break;
0706                        } else {
0707                            ++leftmostNonSpace;
0708                        }
0709                    }
0710                    if (leftmostNonSpace == characterCount) {
0711                        visibleAdvance = 0;
0712                    } else if (leftmostNonSpace == 0) {
0713                        visibleAdvance = lineMetrics.advance;
0714                    } else {
0715                        int logIndex = textLine
0716                                .visualToLogical(leftmostNonSpace);
0717                        float pos = textLine.getCharLinePosition(logIndex);
0718                        visibleAdvance = lineMetrics.advance - pos;
0719                    }
0720                }
0721
0722                // naturalBounds, boundsRect will be generated on demand
0723                naturalBounds = null;
0724                boundsRect = null;
0725
0726                // hashCode will be regenerated on demand
0727                hashCodeCache = 0;
0728
0729                cacheIsValid = true;
0730            }
0731
0732            /**
0733             * The 'natural bounds' encloses all the carets the layout can draw.
0734             * 
0735             */
0736            private Rectangle2D getNaturalBounds() {
0737                ensureCache();
0738
0739                if (naturalBounds == null) {
0740                    naturalBounds = textLine.getItalicBounds();
0741                }
0742
0743                return naturalBounds;
0744            }
0745
0746            /**
0747             * Creates a copy of this <code>TextLayout</code>.
0748             */
0749            protected Object clone() {
0750                /*
0751                 * !!! I think this is safe.  Once created, nothing mutates the
0752                 * glyphvectors or arrays.  But we need to make sure.
0753                 * {jbr} actually, that's not quite true.  The justification code
0754                 * mutates after cloning.  It doesn't actually change the glyphvectors
0755                 * (that's impossible) but it replaces them with justified sets.  This
0756                 * is a problem for GlyphIterator creation, since new GlyphIterators
0757                 * are created by cloning a prototype.  If the prototype has outdated
0758                 * glyphvectors, so will the new ones.  A partial solution is to set the
0759                 * prototypical GlyphIterator to null when the glyphvectors change.  If
0760                 * you forget this one time, you're hosed.
0761                 */
0762                try {
0763                    return super .clone();
0764                } catch (CloneNotSupportedException e) {
0765                    throw new InternalError();
0766                }
0767            }
0768
0769            /*
0770             * Utility to throw an expection if an invalid TextHitInfo is passed
0771             * as a parameter.  Avoids code duplication.
0772             */
0773            private void checkTextHit(TextHitInfo hit) {
0774                if (hit == null) {
0775                    throw new IllegalArgumentException("TextHitInfo is null.");
0776                }
0777
0778                if (hit.getInsertionIndex() < 0
0779                        || hit.getInsertionIndex() > characterCount) {
0780                    throw new IllegalArgumentException(
0781                            "TextHitInfo is out of range");
0782                }
0783            }
0784
0785            /**
0786             * Creates a copy of this <code>TextLayout</code> justified to the 
0787             * specified width.
0788             * <p>
0789             * If this <code>TextLayout</code> has already been justified, an
0790             * exception is thrown.  If this <code>TextLayout</code> object's 
0791             * justification ratio is zero, a <code>TextLayout</code> identical 
0792             * to this <code>TextLayout</code> is returned.
0793             * @param justificationWidth the width to use when justifying the line.
0794             * For best results, it should not be too different from the current
0795             * advance of the line.
0796             * @return a <code>TextLayout</code> justified to the specified width.
0797             * @exception Error if this layout has already been justified, an Error is
0798             * thrown.
0799             */
0800            public TextLayout getJustifiedLayout(float justificationWidth) {
0801
0802                if (justificationWidth <= 0) {
0803                    throw new IllegalArgumentException(
0804                            "justificationWidth <= 0 passed to TextLayout.getJustifiedLayout()");
0805                }
0806
0807                if (justifyRatio == ALREADY_JUSTIFIED) {
0808                    throw new Error("Can't justify again.");
0809                }
0810
0811                ensureCache(); // make sure textLine is not null
0812
0813                // default justification range to exclude trailing logical whitespace
0814                int limit = characterCount;
0815                while (limit > 0 && textLine.isCharWhitespace(limit - 1)) {
0816                    --limit;
0817                }
0818
0819                TextLine newLine = textLine.getJustifiedLine(
0820                        justificationWidth, justifyRatio, 0, limit);
0821                if (newLine != null) {
0822                    return new TextLayout(newLine, baseline, baselineOffsets,
0823                            ALREADY_JUSTIFIED);
0824                }
0825
0826                return this ;
0827            }
0828
0829            /**
0830             * Justify this layout.  Overridden by subclassers to control justification
0831             * (if there were subclassers, that is...)
0832             *
0833             * The layout will only justify if the paragraph attributes (from the
0834             * source text, possibly defaulted by the layout attributes) indicate a
0835             * non-zero justification ratio.  The text will be justified to the
0836             * indicated width.  The current implementation also adjusts hanging
0837             * punctuation and trailing whitespace to overhang the justification width.
0838             * Once justified, the layout may not be rejustified.
0839             * <p>
0840             * Some code may rely on immutablity of layouts.  Subclassers should not
0841             * call this directly, but instead should call getJustifiedLayout, which
0842             * will call this method on a clone of this layout, preserving
0843             * the original.
0844             *
0845             * @param justificationWidth the width to use when justifying the line.
0846             * For best results, it should not be too different from the current
0847             * advance of the line.
0848             * @see #getJustifiedLayout(float)
0849             */
0850            protected void handleJustify(float justificationWidth) {
0851                // never called
0852            }
0853
0854            /**
0855             * Returns the baseline for this <code>TextLayout</code>.
0856             * The baseline is one of the values defined in <code>Font</code>,
0857             * which are roman, centered and hanging.  Ascent and descent are
0858             * relative to this baseline.  The <code>baselineOffsets</code>
0859             * are also relative to this baseline.
0860             * @return the baseline of this <code>TextLayout</code>.
0861             * @see #getBaselineOffsets()
0862             * @see Font
0863             */
0864            public byte getBaseline() {
0865                return baseline;
0866            }
0867
0868            /**
0869             * Returns the offsets array for the baselines used for this 
0870             * <code>TextLayout</code>.
0871             * <p>
0872             * The array is indexed by one of the values defined in 
0873             * <code>Font</code>, which are roman, centered and hanging.  The 
0874             * values are relative to this <code>TextLayout</code> object's 
0875             * baseline, so that <code>getBaselineOffsets[getBaseline()] == 0</code>.
0876             * Offsets are added to the position of the <code>TextLayout</code> 
0877             * object's baseline to get the position for the new baseline.
0878             * @return the offsets array containing the baselines used for this
0879             *    <code>TextLayout</code>.
0880             * @see #getBaseline()
0881             * @see Font
0882             */
0883            public float[] getBaselineOffsets() {
0884                float[] offsets = new float[baselineOffsets.length];
0885                System
0886                        .arraycopy(baselineOffsets, 0, offsets, 0,
0887                                offsets.length);
0888                return offsets;
0889            }
0890
0891            /**
0892             * Returns the advance of this <code>TextLayout</code>.
0893             * The advance is the distance from the origin to the advance of the
0894             * rightmost (bottommost) character.  This is in baseline-relative
0895             * coordinates.
0896             * @return the advance of this <code>TextLayout</code>.
0897             */
0898            public float getAdvance() {
0899                ensureCache();
0900                return lineMetrics.advance;
0901            }
0902
0903            /**
0904             * Returns the advance of this <code>TextLayout</code>, minus trailing 
0905             * whitespace.  This is in baseline-relative coordinates.
0906             * @return the advance of this <code>TextLayout</code> without the
0907             *      trailing whitespace.
0908             * @see #getAdvance()
0909             */
0910            public float getVisibleAdvance() {
0911                ensureCache();
0912                return visibleAdvance;
0913            }
0914
0915            /**
0916             * Returns the ascent of this <code>TextLayout</code>.
0917             * The ascent is the distance from the top (right) of the 
0918             * <code>TextLayout</code> to the baseline.  It is always either 
0919             * positive or zero.  The ascent is sufficient to
0920             * accomodate superscripted text and is the maximum of the sum of the
0921             * ascent, offset, and baseline of each glyph.  The ascent is 
0922             * the maximum ascent from the baseline of all the text in the
0923             * TextLayout.  It is in baseline-relative coordinates.
0924             * @return the ascent of this <code>TextLayout</code>.
0925             */
0926            public float getAscent() {
0927                ensureCache();
0928                return lineMetrics.ascent;
0929            }
0930
0931            /**
0932             * Returns the descent of this <code>TextLayout</code>.
0933             * The descent is the distance from the baseline to the bottom (left) of
0934             * the <code>TextLayout</code>.  It is always either positive or zero.  
0935             * The descent is sufficient to accomodate subscripted text and is the 
0936             * maximum of the sum of the descent, offset, and baseline of each glyph.
0937             * This is the maximum descent from the baseline of all the text in
0938             * the TextLayout.  It is in baseline-relative coordinates.
0939             * @return the descent of this <code>TextLayout</code>.
0940             */
0941            public float getDescent() {
0942                ensureCache();
0943                return lineMetrics.descent;
0944            }
0945
0946            /**
0947             * Returns the leading of the <code>TextLayout</code>.
0948             * The leading is the suggested interline spacing for this 
0949             * <code>TextLayout</code>.  This is in baseline-relative
0950             * coordinates.
0951             * <p>
0952             * The leading is computed from the leading, descent, and baseline
0953             * of all glyphvectors in the <code>TextLayout</code>.  The algorithm 
0954             * is roughly as follows:
0955             * <blockquote><pre>
0956             * maxD = 0;
0957             * maxDL = 0;
0958             * for (GlyphVector g in all glyphvectors) {
0959             *    maxD = max(maxD, g.getDescent() + offsets[g.getBaseline()]);
0960             *    maxDL = max(maxDL, g.getDescent() + g.getLeading() +
0961             *                       offsets[g.getBaseline()]);
0962             * }
0963             * return maxDL - maxD;
0964             * </pre></blockquote>
0965             * @return the leading of this <code>TextLayout</code>.
0966             */
0967            public float getLeading() {
0968                ensureCache();
0969                return lineMetrics.leading;
0970            }
0971
0972            /**
0973             * Returns the bounds of this <code>TextLayout</code>.
0974             * The bounds are in standard coordinates.
0975             * <p>Due to rasterization effects, this bounds might not enclose all of the
0976             * pixels rendered by the TextLayout.</p>
0977             * It might not coincide exactly with the ascent, descent, 
0978             * origin or advance of the <code>TextLayout</code>.
0979             * @return a {@link Rectangle2D} that is the bounds of this
0980             *        <code>TextLayout</code>.
0981             */
0982            public Rectangle2D getBounds() {
0983                ensureCache();
0984
0985                if (boundsRect == null) {
0986                    Rectangle2D vb = textLine.getVisualBounds();
0987                    if (dx != 0 || dy != 0) {
0988                        vb.setRect(vb.getX() - dx, vb.getY() - dy, vb
0989                                .getWidth(), vb.getHeight());
0990                    }
0991                    boundsRect = vb;
0992                }
0993
0994                Rectangle2D bounds = new Rectangle2D.Float();
0995                bounds.setRect(boundsRect);
0996
0997                return bounds;
0998            }
0999
1000            /**
1001             * Returns the pixel bounds of this <code>TextLayout</code> when
1002             * rendered in a graphics with the given
1003             * <code>FontRenderContext</code> at the given location.  The
1004             * graphics render context need not be the same as the
1005             * <code>FontRenderContext</code> used to create this 
1006             * <code>TextLayout</code>, and can be null.  If it is null, the
1007             * <code>FontRenderContext</code> of this <code>TextLayout</code>
1008             * is used.
1009             * @param frc the <code>FontRenderContext</code> of the <code>Graphics</code>.
1010             * @param x the x-coordinate at which to render this <code>TextLayout</code>.
1011             * @param y the y-coordinate at which to render this <code>TextLayout</code>.
1012             * @return a <code>Rectangle</code> bounding the pixels that would be affected.
1013             * @see GlyphVector#getPixelBounds
1014             * @since 1.6
1015             */
1016            public Rectangle getPixelBounds(FontRenderContext frc, float x,
1017                    float y) {
1018                return textLine.getPixelBounds(frc, x, y);
1019            }
1020
1021            /**
1022             * Returns <code>true</code> if this <code>TextLayout</code> has 
1023             * a left-to-right base direction or <code>false</code> if it has
1024             * a right-to-left base direction.  The <code>TextLayout</code>
1025             * has a base direction of either left-to-right (LTR) or
1026             * right-to-left (RTL).  The base direction is independent of the 
1027             * actual direction of text on the line, which may be either LTR, 
1028             * RTL, or mixed. Left-to-right layouts by default should position 
1029             * flush left.  If the layout is on a tabbed line, the
1030             * tabs run left to right, so that logically successive layouts position
1031             * left to right.  The opposite is true for RTL layouts. By default they
1032             * should position flush left, and tabs run right-to-left.
1033             * @return <code>true</code> if the base direction of this 
1034             *         <code>TextLayout</code> is left-to-right; <code>false</code>
1035             *         otherwise.
1036             */
1037            public boolean isLeftToRight() {
1038                return textLine.isDirectionLTR();
1039            }
1040
1041            /**
1042             * Returns <code>true</code> if this <code>TextLayout</code> is vertical.
1043             * @return <code>true</code> if this <code>TextLayout</code> is vertical;
1044             *      <code>false</code> otherwise.
1045             */
1046            public boolean isVertical() {
1047                return isVerticalLine;
1048            }
1049
1050            /**
1051             * Returns the number of characters represented by this 
1052             * <code>TextLayout</code>.
1053             * @return the number of characters in this <code>TextLayout</code>.
1054             */
1055            public int getCharacterCount() {
1056                return characterCount;
1057            }
1058
1059            /*
1060             * carets and hit testing
1061             *
1062             * Positions on a text line are represented by instances of TextHitInfo.
1063             * Any TextHitInfo with characterOffset between 0 and characterCount-1,
1064             * inclusive, represents a valid position on the line.  Additionally,
1065             * [-1, trailing] and [characterCount, leading] are valid positions, and
1066             * represent positions at the logical start and end of the line,
1067             * respectively.
1068             *
1069             * The characterOffsets in TextHitInfo's used and returned by TextLayout
1070             * are relative to the beginning of the text layout, not necessarily to
1071             * the beginning of the text storage the client is using.
1072             *
1073             *
1074             * Every valid TextHitInfo has either one or two carets associated with it.
1075             * A caret is a visual location in the TextLayout indicating where text at
1076             * the TextHitInfo will be displayed on screen.  If a TextHitInfo
1077             * represents a location on a directional boundary, then there are two
1078             * possible visible positions for newly inserted text.  Consider the
1079             * following example, in which capital letters indicate right-to-left text,
1080             * and the overall line direction is left-to-right:
1081             *
1082             * Text Storage: [ a, b, C, D, E, f ]
1083             * Display:        a b E D C f
1084             *
1085             * The text hit info (1, t) represents the trailing side of 'b'.  If 'q',
1086             * a left-to-right character is inserted into the text storage at this
1087             * location, it will be displayed between the 'b' and the 'E':
1088             *
1089             * Text Storage: [ a, b, q, C, D, E, f ]
1090             * Display:        a b q E D C f
1091             *
1092             * However, if a 'W', which is right-to-left, is inserted into the storage
1093             * after 'b', the storage and display will be:
1094             *
1095             * Text Storage: [ a, b, W, C, D, E, f ]
1096             * Display:        a b E D C W f
1097             *
1098             * So, for the original text storage, two carets should be displayed for
1099             * location (1, t): one visually between 'b' and 'E' and one visually
1100             * between 'C' and 'f'.
1101             *
1102             *
1103             * When two carets are displayed for a TextHitInfo, one caret is the
1104             * 'strong' caret and the other is the 'weak' caret.  The strong caret
1105             * indicates where an inserted character will be displayed when that
1106             * character's direction is the same as the direction of the TextLayout.
1107             * The weak caret shows where an character inserted character will be
1108             * displayed when the character's direction is opposite that of the
1109             * TextLayout.
1110             *
1111             *
1112             * Clients should not be overly concerned with the details of correct
1113             * caret display. TextLayout.getCaretShapes(TextHitInfo) will return an
1114             * array of two paths representing where carets should be displayed.
1115             * The first path in the array is the strong caret; the second element,
1116             * if non-null, is the weak caret.  If the second element is null,
1117             * then there is no weak caret for the given TextHitInfo.
1118             *
1119             *
1120             * Since text can be visually reordered, logically consecutive
1121             * TextHitInfo's may not be visually consecutive.  One implication of this
1122             * is that a client cannot tell from inspecting a TextHitInfo whether the
1123             * hit represents the first (or last) caret in the layout.  Clients
1124             * can call getVisualOtherHit();  if the visual companion is
1125             * (-1, TRAILING) or (characterCount, LEADING), then the hit is at the
1126             * first (last) caret position in the layout.
1127             */
1128
1129            private float[] getCaretInfo(int caret, Rectangle2D bounds,
1130                    float[] info) {
1131
1132                float top1X, top2X;
1133                float bottom1X, bottom2X;
1134
1135                if (caret == 0 || caret == characterCount) {
1136
1137                    float pos;
1138                    int logIndex;
1139                    if (caret == characterCount) {
1140                        logIndex = textLine.visualToLogical(characterCount - 1);
1141                        pos = textLine.getCharLinePosition(logIndex)
1142                                + textLine.getCharAdvance(logIndex);
1143                    } else {
1144                        logIndex = textLine.visualToLogical(caret);
1145                        pos = textLine.getCharLinePosition(logIndex);
1146                    }
1147                    float angle = textLine.getCharAngle(logIndex);
1148                    float shift = textLine.getCharShift(logIndex);
1149                    pos += angle * shift;
1150                    top1X = top2X = pos + angle
1151                            * textLine.getCharAscent(logIndex);
1152                    bottom1X = bottom2X = pos - angle
1153                            * textLine.getCharDescent(logIndex);
1154                } else {
1155
1156                    {
1157                        int logIndex = textLine.visualToLogical(caret - 1);
1158                        float angle1 = textLine.getCharAngle(logIndex);
1159                        float pos1 = textLine.getCharLinePosition(logIndex)
1160                                + textLine.getCharAdvance(logIndex);
1161                        if (angle1 != 0) {
1162                            pos1 += angle1 * textLine.getCharShift(logIndex);
1163                            top1X = pos1 + angle1
1164                                    * textLine.getCharAscent(logIndex);
1165                            bottom1X = pos1 - angle1
1166                                    * textLine.getCharDescent(logIndex);
1167                        } else {
1168                            top1X = bottom1X = pos1;
1169                        }
1170                    }
1171                    {
1172                        int logIndex = textLine.visualToLogical(caret);
1173                        float angle2 = textLine.getCharAngle(logIndex);
1174                        float pos2 = textLine.getCharLinePosition(logIndex);
1175                        if (angle2 != 0) {
1176                            pos2 += angle2 * textLine.getCharShift(logIndex);
1177                            top2X = pos2 + angle2
1178                                    * textLine.getCharAscent(logIndex);
1179                            bottom2X = pos2 - angle2
1180                                    * textLine.getCharDescent(logIndex);
1181                        } else {
1182                            top2X = bottom2X = pos2;
1183                        }
1184                    }
1185                }
1186
1187                float topX = (top1X + top2X) / 2;
1188                float bottomX = (bottom1X + bottom2X) / 2;
1189
1190                if (info == null) {
1191                    info = new float[2];
1192                }
1193
1194                if (isVerticalLine) {
1195                    info[1] = (float) ((topX - bottomX) / bounds.getWidth());
1196                    info[0] = (float) (topX + (info[1] * bounds.getX()));
1197                } else {
1198                    info[1] = (float) ((topX - bottomX) / bounds.getHeight());
1199                    info[0] = (float) (bottomX + (info[1] * bounds.getMaxY()));
1200                }
1201
1202                return info;
1203            }
1204
1205            /**
1206             * Returns information about the caret corresponding to <code>hit</code>.
1207             * The first element of the array is the intersection of the caret with
1208             * the baseline, as a distance along the baseline. The second element
1209             * of the array is the inverse slope (run/rise) of the caret, measured
1210             * with respect to the baseline at that point.
1211             * <p>
1212             * This method is meant for informational use.  To display carets, it
1213             * is better to use <code>getCaretShapes</code>.
1214             * @param hit a hit on a character in this <code>TextLayout</code>
1215             * @param bounds the bounds to which the caret info is constructed.
1216             *     The bounds is in baseline-relative coordinates.
1217             * @return a two-element array containing the position and slope of
1218             * the caret.  The returned caret info is in baseline-relative coordinates.
1219             * @see #getCaretShapes(int, Rectangle2D, TextLayout.CaretPolicy)
1220             * @see Font#getItalicAngle
1221             */
1222            public float[] getCaretInfo(TextHitInfo hit, Rectangle2D bounds) {
1223                ensureCache();
1224                checkTextHit(hit);
1225
1226                return getCaretInfoTestInternal(hit, bounds);
1227            }
1228
1229            // this version provides extra info in the float array
1230            // the first two values are as above
1231            // the next four values are the endpoints of the caret, as computed
1232            // using the hit character's offset (baseline + ssoffset) and 
1233            // natural ascent and descent.
1234            // these  values are trimmed to the bounds where required to fit,
1235            // but otherwise independent of it.
1236            private float[] getCaretInfoTestInternal(TextHitInfo hit,
1237                    Rectangle2D bounds) {
1238                ensureCache();
1239                checkTextHit(hit);
1240
1241                float[] info = new float[6];
1242
1243                // get old data first
1244                getCaretInfo(hitToCaret(hit), bounds, info);
1245
1246                // then add our new data
1247                double iangle, ixbase, p1x, p1y, p2x, p2y;
1248
1249                int charix = hit.getCharIndex();
1250                boolean lead = hit.isLeadingEdge();
1251                boolean ltr = textLine.isDirectionLTR();
1252                boolean horiz = !isVertical();
1253
1254                if (charix == -1 || charix == characterCount) {
1255                    // !!! note: want non-shifted, baseline ascent and descent here!
1256                    // TextLine should return appropriate line metrics object for these values
1257                    TextLineMetrics m = textLine.getMetrics();
1258                    boolean low = ltr == (charix == -1);
1259                    iangle = 0;
1260                    if (horiz) {
1261                        p1x = p2x = low ? 0 : m.advance;
1262                        p1y = -m.ascent;
1263                        p2y = m.descent;
1264                    } else {
1265                        p1y = p2y = low ? 0 : m.advance;
1266                        p1x = m.descent;
1267                        p2x = m.ascent;
1268                    }
1269                } else {
1270                    CoreMetrics this cm = textLine.getCoreMetricsAt(charix);
1271                    iangle = this cm.italicAngle;
1272                    ixbase = textLine.getCharLinePosition(charix, lead);
1273                    if (this cm.baselineIndex < 0) {
1274                        // this is a graphic, no italics, use entire line height for caret
1275                        TextLineMetrics m = textLine.getMetrics();
1276                        if (horiz) {
1277                            p1x = p2x = ixbase;
1278                            if (this cm.baselineIndex == GraphicAttribute.TOP_ALIGNMENT) {
1279                                p1y = -m.ascent;
1280                                p2y = p1y + this cm.height;
1281                            } else {
1282                                p2y = m.descent;
1283                                p1y = p2y - this cm.height;
1284                            }
1285                        } else {
1286                            p1y = p2y = ixbase;
1287                            p1x = m.descent;
1288                            p2x = m.ascent;
1289                            // !!! top/bottom adjustment not implemented for vertical
1290                        }
1291                    } else {
1292                        float bo = baselineOffsets[this cm.baselineIndex];
1293                        if (horiz) {
1294                            ixbase += iangle * this cm.ssOffset;
1295                            p1x = ixbase + iangle * this cm.ascent;
1296                            p2x = ixbase - iangle * this cm.descent;
1297                            p1y = bo - this cm.ascent;
1298                            p2y = bo + this cm.descent;
1299                        } else {
1300                            ixbase -= iangle * this cm.ssOffset;
1301                            p1y = ixbase + iangle * this cm.ascent;
1302                            p2y = ixbase - iangle * this cm.descent;
1303                            p1x = bo + this cm.ascent;
1304                            p2x = bo + this cm.descent;
1305                        }
1306                    }
1307                }
1308
1309                info[2] = (float) p1x;
1310                info[3] = (float) p1y;
1311                info[4] = (float) p2x;
1312                info[5] = (float) p2y;
1313
1314                return info;
1315            }
1316
1317            /**
1318             * Returns information about the caret corresponding to <code>hit</code>.
1319             * This method is a convenience overload of <code>getCaretInfo</code> and
1320             * uses the natural bounds of this <code>TextLayout</code>.
1321             * @param hit a hit on a character in this <code>TextLayout</code>
1322             * @return the information about a caret corresponding to a hit.  The
1323             *     returned caret info is in baseline-relative coordinates.
1324             */
1325            public float[] getCaretInfo(TextHitInfo hit) {
1326
1327                return getCaretInfo(hit, getNaturalBounds());
1328            }
1329
1330            /**
1331             * Returns a caret index corresponding to <code>hit</code>.
1332             * Carets are numbered from left to right (top to bottom) starting from
1333             * zero. This always places carets next to the character hit, on the
1334             * indicated side of the character.
1335             * @param hit a hit on a character in this <code>TextLayout</code>
1336             * @return a caret index corresponding to the specified hit.
1337             */
1338            private int hitToCaret(TextHitInfo hit) {
1339
1340                int hitIndex = hit.getCharIndex();
1341
1342                if (hitIndex < 0) {
1343                    return textLine.isDirectionLTR() ? 0 : characterCount;
1344                } else if (hitIndex >= characterCount) {
1345                    return textLine.isDirectionLTR() ? characterCount : 0;
1346                }
1347
1348                int visIndex = textLine.logicalToVisual(hitIndex);
1349
1350                if (hit.isLeadingEdge() != textLine.isCharLTR(hitIndex)) {
1351                    ++visIndex;
1352                }
1353
1354                return visIndex;
1355            }
1356
1357            /**
1358             * Given a caret index, return a hit whose caret is at the index.
1359             * The hit is NOT guaranteed to be strong!!!
1360             *
1361             * @param caret a caret index.
1362             * @return a hit on this layout whose strong caret is at the requested
1363             * index.
1364             */
1365            private TextHitInfo caretToHit(int caret) {
1366
1367                if (caret == 0 || caret == characterCount) {
1368
1369                    if ((caret == characterCount) == textLine.isDirectionLTR()) {
1370                        return TextHitInfo.leading(characterCount);
1371                    } else {
1372                        return TextHitInfo.trailing(-1);
1373                    }
1374                } else {
1375
1376                    int charIndex = textLine.visualToLogical(caret);
1377                    boolean leading = textLine.isCharLTR(charIndex);
1378
1379                    return leading ? TextHitInfo.leading(charIndex)
1380                            : TextHitInfo.trailing(charIndex);
1381                }
1382            }
1383
1384            private boolean caretIsValid(int caret) {
1385
1386                if (caret == characterCount || caret == 0) {
1387                    return true;
1388                }
1389
1390                int offset = textLine.visualToLogical(caret);
1391
1392                if (!textLine.isCharLTR(offset)) {
1393                    offset = textLine.visualToLogical(caret - 1);
1394                    if (textLine.isCharLTR(offset)) {
1395                        return true;
1396                    }
1397                }
1398
1399                // At this point, the leading edge of the character
1400                // at offset is at the given caret.
1401
1402                return textLine.caretAtOffsetIsValid(offset);
1403            }
1404
1405            /**
1406             * Returns the hit for the next caret to the right (bottom); if there 
1407             * is no such hit, returns <code>null</code>.
1408             * If the hit character index is out of bounds, an 
1409             * {@link IllegalArgumentException} is thrown.
1410             * @param hit a hit on a character in this layout
1411             * @return a hit whose caret appears at the next position to the
1412             * right (bottom) of the caret of the provided hit or <code>null</code>.
1413             */
1414            public TextHitInfo getNextRightHit(TextHitInfo hit) {
1415                ensureCache();
1416                checkTextHit(hit);
1417
1418                int caret = hitToCaret(hit);
1419
1420                if (caret == characterCount) {
1421                    return null;
1422                }
1423
1424                do {
1425                    ++caret;
1426                } while (!caretIsValid(caret));
1427
1428                return caretToHit(caret);
1429            }
1430
1431            /**
1432             * Returns the hit for the next caret to the right (bottom); if no
1433             * such hit, returns <code>null</code>.  The hit is to the right of 
1434             * the strong caret at the specified offset, as determined by the
1435             * specified policy.
1436             * The returned hit is the stronger of the two possible
1437             * hits, as determined by the specified policy.
1438             * @param offset an insertion offset in this <code>TextLayout</code>.
1439             * Cannot be less than 0 or greater than this <code>TextLayout</code> 
1440             * object's character count.
1441             * @param policy the policy used to select the strong caret
1442             * @return a hit whose caret appears at the next position to the
1443             * right (bottom) of the caret of the provided hit, or <code>null</code>.
1444             */
1445            public TextHitInfo getNextRightHit(int offset, CaretPolicy policy) {
1446
1447                if (offset < 0 || offset > characterCount) {
1448                    throw new IllegalArgumentException(
1449                            "Offset out of bounds in TextLayout.getNextRightHit()");
1450                }
1451
1452                if (policy == null) {
1453                    throw new IllegalArgumentException(
1454                            "Null CaretPolicy passed to TextLayout.getNextRightHit()");
1455                }
1456
1457                TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
1458                TextHitInfo hit2 = hit1.getOtherHit();
1459
1460                TextHitInfo nextHit = getNextRightHit(policy.getStrongCaret(
1461                        hit1, hit2, this ));
1462
1463                if (nextHit != null) {
1464                    TextHitInfo otherHit = getVisualOtherHit(nextHit);
1465                    return policy.getStrongCaret(otherHit, nextHit, this );
1466                } else {
1467                    return null;
1468                }
1469            }
1470
1471            /**
1472             * Returns the hit for the next caret to the right (bottom); if no
1473             * such hit, returns <code>null</code>.  The hit is to the right of
1474             * the strong caret at the specified offset, as determined by the 
1475             * default policy.
1476             * The returned hit is the stronger of the two possible
1477             * hits, as determined by the default policy.
1478             * @param offset an insertion offset in this <code>TextLayout</code>.
1479             * Cannot be less than 0 or greater than the <code>TextLayout</code>
1480             * object's character count.
1481             * @return a hit whose caret appears at the next position to the
1482             * right (bottom) of the caret of the provided hit, or <code>null</code>.
1483             */
1484            public TextHitInfo getNextRightHit(int offset) {
1485
1486                return getNextRightHit(offset, DEFAULT_CARET_POLICY);
1487            }
1488
1489            /**
1490             * Returns the hit for the next caret to the left (top); if no such
1491             * hit, returns <code>null</code>.
1492             * If the hit character index is out of bounds, an 
1493             * <code>IllegalArgumentException</code> is thrown.
1494             * @param hit a hit on a character in this <code>TextLayout</code>.
1495             * @return a hit whose caret appears at the next position to the
1496             * left (top) of the caret of the provided hit, or <code>null</code>.
1497             */
1498            public TextHitInfo getNextLeftHit(TextHitInfo hit) {
1499                ensureCache();
1500                checkTextHit(hit);
1501
1502                int caret = hitToCaret(hit);
1503
1504                if (caret == 0) {
1505                    return null;
1506                }
1507
1508                do {
1509                    --caret;
1510                } while (!caretIsValid(caret));
1511
1512                return caretToHit(caret);
1513            }
1514
1515            /**
1516             * Returns the hit for the next caret to the left (top); if no
1517             * such hit, returns <code>null</code>.  The hit is to the left of
1518             * the strong caret at the specified offset, as determined by the
1519             * specified policy.
1520             * The returned hit is the stronger of the two possible
1521             * hits, as determined by the specified policy.
1522             * @param offset an insertion offset in this <code>TextLayout</code>.
1523             * Cannot be less than 0 or greater than this <code>TextLayout</code>
1524             * object's character count.
1525             * @param policy the policy used to select the strong caret
1526             * @return a hit whose caret appears at the next position to the
1527             * left (top) of the caret of the provided hit, or <code>null</code>.
1528             */
1529            public TextHitInfo getNextLeftHit(int offset, CaretPolicy policy) {
1530
1531                if (policy == null) {
1532                    throw new IllegalArgumentException(
1533                            "Null CaretPolicy passed to TextLayout.getNextLeftHit()");
1534                }
1535
1536                if (offset < 0 || offset > characterCount) {
1537                    throw new IllegalArgumentException(
1538                            "Offset out of bounds in TextLayout.getNextLeftHit()");
1539                }
1540
1541                TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
1542                TextHitInfo hit2 = hit1.getOtherHit();
1543
1544                TextHitInfo nextHit = getNextLeftHit(policy.getStrongCaret(
1545                        hit1, hit2, this ));
1546
1547                if (nextHit != null) {
1548                    TextHitInfo otherHit = getVisualOtherHit(nextHit);
1549                    return policy.getStrongCaret(otherHit, nextHit, this );
1550                } else {
1551                    return null;
1552                }
1553            }
1554
1555            /**
1556             * Returns the hit for the next caret to the left (top); if no
1557             * such hit, returns <code>null</code>.  The hit is to the left of
1558             * the strong caret at the specified offset, as determined by the
1559             * default policy.
1560             * The returned hit is the stronger of the two possible
1561             * hits, as determined by the default policy.
1562             * @param offset an insertion offset in this <code>TextLayout</code>.  
1563             * Cannot be less than 0 or greater than this <code>TextLayout</code>
1564             * object's character count.
1565             * @return a hit whose caret appears at the next position to the
1566             * left (top) of the caret of the provided hit, or <code>null</code>.
1567             */
1568            public TextHitInfo getNextLeftHit(int offset) {
1569
1570                return getNextLeftHit(offset, DEFAULT_CARET_POLICY);
1571            }
1572
1573            /**
1574             * Returns the hit on the opposite side of the specified hit's caret.
1575             * @param hit the specified hit
1576             * @return a hit that is on the opposite side of the specified hit's
1577             *    caret.
1578             */
1579            public TextHitInfo getVisualOtherHit(TextHitInfo hit) {
1580
1581                ensureCache();
1582                checkTextHit(hit);
1583
1584                int hitCharIndex = hit.getCharIndex();
1585
1586                int charIndex;
1587                boolean leading;
1588
1589                if (hitCharIndex == -1 || hitCharIndex == characterCount) {
1590
1591                    int visIndex;
1592                    if (textLine.isDirectionLTR() == (hitCharIndex == -1)) {
1593                        visIndex = 0;
1594                    } else {
1595                        visIndex = characterCount - 1;
1596                    }
1597
1598                    charIndex = textLine.visualToLogical(visIndex);
1599
1600                    if (textLine.isDirectionLTR() == (hitCharIndex == -1)) {
1601                        // at left end
1602                        leading = textLine.isCharLTR(charIndex);
1603                    } else {
1604                        // at right end
1605                        leading = !textLine.isCharLTR(charIndex);
1606                    }
1607                } else {
1608
1609                    int visIndex = textLine.logicalToVisual(hitCharIndex);
1610
1611                    boolean movedToRight;
1612                    if (textLine.isCharLTR(hitCharIndex) == hit.isLeadingEdge()) {
1613                        --visIndex;
1614                        movedToRight = false;
1615                    } else {
1616                        ++visIndex;
1617                        movedToRight = true;
1618                    }
1619
1620                    if (visIndex > -1 && visIndex < characterCount) {
1621                        charIndex = textLine.visualToLogical(visIndex);
1622                        leading = movedToRight == textLine.isCharLTR(charIndex);
1623                    } else {
1624                        charIndex = (movedToRight == textLine.isDirectionLTR()) ? characterCount
1625                                : -1;
1626                        leading = charIndex == characterCount;
1627                    }
1628                }
1629
1630                return leading ? TextHitInfo.leading(charIndex) : TextHitInfo
1631                        .trailing(charIndex);
1632            }
1633
1634            private double[] getCaretPath(TextHitInfo hit, Rectangle2D bounds) {
1635                float[] info = getCaretInfo(hit, bounds);
1636                return new double[] { info[2], info[3], info[4], info[5] };
1637            }
1638
1639            /**
1640             * Return an array of four floats corresponding the endpoints of the caret
1641             * x0, y0, x1, y1.
1642             *
1643             * This creates a line along the slope of the caret intersecting the
1644             * baseline at the caret
1645             * position, and extending from ascent above the baseline to descent below
1646             * it.
1647             */
1648            private double[] getCaretPath(int caret, Rectangle2D bounds,
1649                    boolean clipToBounds) {
1650
1651                float[] info = getCaretInfo(caret, bounds, null);
1652
1653                double pos = info[0];
1654                double slope = info[1];
1655
1656                double x0, y0, x1, y1;
1657                double x2 = -3141.59, y2 = -2.7; // values are there to make compiler happy
1658
1659                double left = bounds.getX();
1660                double right = left + bounds.getWidth();
1661                double top = bounds.getY();
1662                double bottom = top + bounds.getHeight();
1663
1664                boolean threePoints = false;
1665
1666                if (isVerticalLine) {
1667
1668                    if (slope >= 0) {
1669                        x0 = left;
1670                        x1 = right;
1671                    } else {
1672                        x1 = left;
1673                        x0 = right;
1674                    }
1675
1676                    y0 = pos + x0 * slope;
1677                    y1 = pos + x1 * slope;
1678
1679                    // y0 <= y1, always
1680
1681                    if (clipToBounds) {
1682                        if (y0 < top) {
1683                            if (slope <= 0 || y1 <= top) {
1684                                y0 = y1 = top;
1685                            } else {
1686                                threePoints = true;
1687                                y0 = top;
1688                                y2 = top;
1689                                x2 = x1 + (top - y1) / slope;
1690                                if (y1 > bottom) {
1691                                    y1 = bottom;
1692                                }
1693                            }
1694                        } else if (y1 > bottom) {
1695                            if (slope >= 0 || y0 >= bottom) {
1696                                y0 = y1 = bottom;
1697                            } else {
1698                                threePoints = true;
1699                                y1 = bottom;
1700                                y2 = bottom;
1701                                x2 = x0 + (bottom - x1) / slope;
1702                            }
1703                        }
1704                    }
1705
1706                } else {
1707
1708                    if (slope >= 0) {
1709                        y0 = bottom;
1710                        y1 = top;
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                            } else {
1726                                threePoints = true;
1727                                x0 = left;
1728                                x2 = left;
1729                                y2 = y1 - (left - x1) / slope;
1730                                if (x1 > right) {
1731                                    x1 = right;
1732                                }
1733                            }
1734                        } else if (x1 > right) {
1735                            if (slope >= 0 || x0 >= right) {
1736                                x0 = x1 = right;
1737                            } else {
1738                                threePoints = true;
1739                                x1 = right;
1740                                x2 = right;
1741                                y2 = y0 - (right - x0) / slope;
1742                            }
1743                        }
1744                    }
1745                }
1746
1747                return threePoints ? new double[] { x0, y0, x2, y2, x1, y1 }
1748                        : new double[] { x0, y0, x1, y1 };
1749            }
1750
1751            private static GeneralPath pathToShape(double[] path,
1752                    boolean close, LayoutPathImpl lp) {
1753                GeneralPath result = new GeneralPath(GeneralPath.WIND_EVEN_ODD,
1754                        path.length);
1755                result.moveTo((float) path[0], (float) path[1]);
1756                for (int i = 2; i < path.length; i += 2) {
1757                    result.lineTo((float) path[i], (float) path[i + 1]);
1758                }
1759                if (close) {
1760                    result.closePath();
1761                }
1762
1763                if (lp != null) {
1764                    result = (GeneralPath) lp.mapShape(result);
1765                }
1766                return result;
1767            }
1768
1769            /**
1770             * Returns a {@link Shape} representing the caret at the specified 
1771             * hit inside the specified bounds.
1772             * @param hit the hit at which to generate the caret
1773             * @param bounds the bounds of the <code>TextLayout</code> to use 
1774             *    in generating the caret.  The bounds is in baseline-relative 
1775             *    coordinates.
1776             * @return a <code>Shape</code> representing the caret.  The returned
1777             *    shape is in standard coordinates.
1778             */
1779            public Shape getCaretShape(TextHitInfo hit, Rectangle2D bounds) {
1780                ensureCache();
1781                checkTextHit(hit);
1782
1783                if (bounds == null) {
1784                    throw new IllegalArgumentException(
1785                            "Null Rectangle2D passed to TextLayout.getCaret()");
1786                }
1787
1788                return pathToShape(getCaretPath(hit, bounds), false, textLine
1789                        .getLayoutPath());
1790            }
1791
1792            /**
1793             * Returns a <code>Shape</code> representing the caret at the specified
1794             * hit inside the natural bounds of this <code>TextLayout</code>.
1795             * @param hit the hit at which to generate the caret
1796             * @return a <code>Shape</code> representing the caret.  The returned
1797             *     shape is in standard coordinates.
1798             */
1799            public Shape getCaretShape(TextHitInfo hit) {
1800
1801                return getCaretShape(hit, getNaturalBounds());
1802            }
1803
1804            /**
1805             * Return the "stronger" of the TextHitInfos.  The TextHitInfos
1806             * should be logical or visual counterparts.  They are not
1807             * checked for validity.
1808             */
1809            private final TextHitInfo getStrongHit(TextHitInfo hit1,
1810                    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                    } else {
1826                        return hit1;
1827                    }
1828                } else {
1829                    return (hit1Level < hit2Level) ? hit1 : hit2;
1830                }
1831            }
1832
1833            /**
1834             * Returns the level of the character at <code>index</code>.  
1835             * Indices -1 and <code>characterCount</code> are assigned the base
1836             * level of this <code>TextLayout</code>.
1837             * @param index the index of the character from which to get the level
1838             * @return the level of the character at the specified index.
1839             */
1840            public byte getCharacterLevel(int index) {
1841
1842                // hmm, allow indices at endpoints?  For now, yes.
1843                if (index < -1 || index > characterCount) {
1844                    throw new IllegalArgumentException(
1845                            "Index is out of range in getCharacterLevel.");
1846                }
1847
1848                ensureCache();
1849                if (index == -1 || index == characterCount) {
1850                    return (byte) (textLine.isDirectionLTR() ? 0 : 1);
1851                }
1852
1853                return textLine.getCharLevel(index);
1854            }
1855
1856            /**
1857             * Returns two paths corresponding to the strong and weak caret.
1858             * @param offset an offset in this <code>TextLayout</code>
1859             * @param bounds the bounds to which to extend the carets.  The
1860             * bounds is in baseline-relative coordinates.
1861             * @param policy the specified <code>CaretPolicy</code>
1862             * @return an array of two paths.  Element zero is the strong
1863             * caret.  If there are two carets, element one is the weak caret,
1864             * otherwise it is <code>null</code>. The returned shapes
1865             * are in standard coordinates.
1866             */
1867            public Shape[] getCaretShapes(int offset, Rectangle2D bounds,
1868                    CaretPolicy policy) {
1869
1870                ensureCache();
1871
1872                if (offset < 0 || offset > characterCount) {
1873                    throw new IllegalArgumentException(
1874                            "Offset out of bounds in TextLayout.getCaretShapes()");
1875                }
1876
1877                if (bounds == null) {
1878                    throw new IllegalArgumentException(
1879                            "Null Rectangle2D passed to TextLayout.getCaretShapes()");
1880                }
1881
1882                if (policy == null) {
1883                    throw new IllegalArgumentException(
1884                            "Null CaretPolicy passed to TextLayout.getCaretShapes()");
1885                }
1886
1887                Shape[] result = new Shape[2];
1888
1889                TextHitInfo hit = TextHitInfo.afterOffset(offset);
1890
1891                int hitCaret = hitToCaret(hit);
1892
1893                LayoutPathImpl lp = textLine.getLayoutPath();
1894                Shape hitShape = pathToShape(getCaretPath(hit, bounds), false,
1895                        lp);
1896                TextHitInfo otherHit = hit.getOtherHit();
1897                int otherCaret = hitToCaret(otherHit);
1898
1899                if (hitCaret == otherCaret) {
1900                    result[0] = hitShape;
1901                } else { // more than one caret
1902                    Shape otherShape = pathToShape(getCaretPath(otherHit,
1903                            bounds), false, lp);
1904
1905                    TextHitInfo strongHit = policy.getStrongCaret(hit,
1906                            otherHit, this );
1907                    boolean hitIsStrong = strongHit.equals(hit);
1908
1909                    if (hitIsStrong) {// then other is weak
1910                        result[0] = hitShape;
1911                        result[1] = otherShape;
1912                    } else {
1913                        result[0] = otherShape;
1914                        result[1] = hitShape;
1915                    }
1916                }
1917
1918                return result;
1919            }
1920
1921            /**
1922             * Returns two paths corresponding to the strong and weak caret.  
1923             * This method is a convenience overload of <code>getCaretShapes</code>
1924             * that uses the default caret policy.
1925             * @param offset an offset in this <code>TextLayout</code>
1926             * @param bounds the bounds to which to extend the carets.  This is
1927             *     in baseline-relative coordinates.
1928             * @return two paths corresponding to the strong and weak caret as
1929             *    defined by the <code>DEFAULT_CARET_POLICY</code>.  These are
1930             *    in standard coordinates.
1931             */
1932            public Shape[] getCaretShapes(int offset, Rectangle2D bounds) {
1933                // {sfb} parameter checking is done in overloaded version
1934                return getCaretShapes(offset, bounds, DEFAULT_CARET_POLICY);
1935            }
1936
1937            /**
1938             * Returns two paths corresponding to the strong and weak caret.  
1939             * This method is a convenience overload of <code>getCaretShapes</code>
1940             * that uses the default caret policy and this <code>TextLayout</code>
1941             * object's natural bounds.  
1942             * @param offset an offset in this <code>TextLayout</code>
1943             * @return two paths corresponding to the strong and weak caret as
1944             *    defined by the <code>DEFAULT_CARET_POLICY</code>.  These are
1945             *    in standard coordinates.
1946             */
1947            public Shape[] getCaretShapes(int offset) {
1948                // {sfb} parameter checking is done in overloaded version
1949                return getCaretShapes(offset, getNaturalBounds(),
1950                        DEFAULT_CARET_POLICY);
1951            }
1952
1953            // A utility to return a path enclosing the given path
1954            // Path0 must be left or top of path1
1955            // {jbr} no assumptions about size of path0, path1 anymore.
1956            private GeneralPath boundingShape(double[] path0, double[] path1) {
1957
1958                // Really, we want the path to be a convex hull around all of the
1959                // points in path0 and path1.  But we can get by with less than
1960                // that.  We do need to prevent the two segments which
1961                // join path0 to path1 from crossing each other.  So, if we
1962                // traverse path0 from top to bottom, we'll traverse path1 from
1963                // bottom to top (and vice versa).
1964
1965                GeneralPath result = pathToShape(path0, false, null);
1966
1967                boolean sameDirection;
1968
1969                if (isVerticalLine) {
1970                    sameDirection = (path0[1] > path0[path0.length - 1]) == (path1[1] > path1[path1.length - 1]);
1971                } else {
1972                    sameDirection = (path0[0] > path0[path0.length - 2]) == (path1[0] > path1[path1.length - 2]);
1973                }
1974
1975                int start;
1976                int limit;
1977                int increment;
1978
1979                if (sameDirection) {
1980                    start = path1.length - 2;
1981                    limit = -2;
1982                    increment = -2;
1983                } else {
1984                    start = 0;
1985                    limit = path1.length;
1986                    increment = 2;
1987                }
1988
1989                for (int i = start; i != limit; i += increment) {
1990                    result.lineTo((float) path1[i], (float) path1[i + 1]);
1991                }
1992
1993                result.closePath();
1994
1995                return result;
1996            }
1997
1998            // A utility to convert a pair of carets into a bounding path
1999            // {jbr} Shape is never outside of bounds.
2000            private GeneralPath caretBoundingShape(int caret0, 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(), bounds.getY() };
2024                } else {
2025                    path0 = new double[] { bounds.getX(),
2026                            bounds.getY() + bounds.getHeight(), bounds.getX(),
2027                            bounds.getY() };
2028                }
2029
2030                double[] path1 = getCaretPath(0, bounds, true);
2031
2032                return boundingShape(path0, path1);
2033            }
2034
2035            /*
2036             * A utility to return the path bounding the area to the right (bottom) of
2037             * the layout.
2038             */
2039            private GeneralPath rightShape(Rectangle2D bounds) {
2040                double[] path1;
2041                if (isVerticalLine) {
2042                    path1 = new double[] { bounds.getX(),
2043                            bounds.getY() + bounds.getHeight(),
2044                            bounds.getX() + bounds.getWidth(),
2045                            bounds.getY() + bounds.getHeight() };
2046                } else {
2047                    path1 = new double[] { bounds.getX() + bounds.getWidth(),
2048                            bounds.getY() + bounds.getHeight(),
2049                            bounds.getX() + bounds.getWidth(), bounds.getY() };
2050                }
2051
2052                double[] path0 = getCaretPath(characterCount, bounds, true);
2053
2054                return boundingShape(path0, path1);
2055            }
2056
2057            /**
2058             * Returns the logical ranges of text corresponding to a visual selection.
2059             * @param firstEndpoint an endpoint of the visual range
2060             * @param secondEndpoint the other endpoint of the visual range.
2061             * This endpoint can be less than <code>firstEndpoint</code>.
2062             * @return an array of integers representing start/limit pairs for the
2063             * selected ranges.
2064             * @see #getVisualHighlightShape(TextHitInfo, TextHitInfo, Rectangle2D)
2065             */
2066            public int[] getLogicalRangesForVisualSelection(
2067                    TextHitInfo firstEndpoint, TextHitInfo secondEndpoint) {
2068                ensureCache();
2069
2070                checkTextHit(firstEndpoint);
2071                checkTextHit(secondEndpoint);
2072
2073                // !!! probably want to optimize for all LTR text
2074
2075                boolean[] included = new boolean[characterCount];
2076
2077                int startIndex = hitToCaret(firstEndpoint);
2078                int limitIndex = hitToCaret(secondEndpoint);
2079
2080                if (startIndex > limitIndex) {
2081                    int t = startIndex;
2082                    startIndex = limitIndex;
2083                    limitIndex = t;
2084                }
2085
2086                /*
2087                 * now we have the visual indexes of the glyphs at the start and limit
2088                 * of the selection range walk through runs marking characters that
2089                 * were included in the visual range there is probably a more efficient
2090                 * way to do this, but this ought to work, so hey
2091                 */
2092
2093                if (startIndex < limitIndex) {
2094                    int visIndex = startIndex;
2095                    while (visIndex < limitIndex) {
2096                        included[textLine.visualToLogical(visIndex)] = true;
2097                        ++visIndex;
2098                    }
2099                }
2100
2101                /*
2102                 * count how many runs we have, ought to be one or two, but perhaps
2103                 * things are especially weird
2104                 */
2105                int count = 0;
2106                boolean inrun = false;
2107                for (int i = 0; i < characterCount; i++) {
2108                    if (included[i] != inrun) {
2109                        inrun = !inrun;
2110                        if (inrun) {
2111                            count++;
2112                        }
2113                    }
2114                }
2115
2116                int[] ranges = new int[count * 2];
2117                count = 0;
2118                inrun = false;
2119                for (int i = 0; i < characterCount; i++) {
2120                    if (included[i] != inrun) {
2121                        ranges[count++] = i;
2122                        inrun = !inrun;
2123                    }
2124                }
2125                if (inrun) {
2126                    ranges[count++] = characterCount;
2127                }
2128
2129                return ranges;
2130            }
2131
2132            /**
2133             * Returns a path enclosing the visual selection in the specified range,
2134             * extended to <code>bounds</code>.
2135             * <p>
2136             * If the selection includes the leftmost (topmost) position, the selection
2137             * is extended to the left (top) of <code>bounds</code>.  If the 
2138             * selection includes the rightmost (bottommost) position, the selection
2139             * is extended to the right (bottom) of the bounds.  The height 
2140             * (width on vertical lines) of the selection is always extended to 
2141             * <code>bounds</code>.
2142             * <p>
2143             * Although the selection is always contiguous, the logically selected
2144             * text can be discontiguous on lines with mixed-direction text.  The
2145             * logical ranges of text selected can be retrieved using
2146             * <code>getLogicalRangesForVisualSelection</code>.  For example, 
2147             * consider the text 'ABCdef' where capital letters indicate 
2148             * right-to-left text, rendered on a right-to-left line, with a visual
2149             * selection from 0L (the leading edge of 'A') to 3T (the trailing edge
2150             * of 'd').  The text appears as follows, with bold underlined areas
2151             * representing the selection:
2152             * <br><pre>
2153             *    d<u><b>efCBA  </b></u>
2154             * </pre>
2155             * The logical selection ranges are 0-3, 4-6 (ABC, ef) because the
2156             * visually contiguous text is logically discontiguous.  Also note that
2157             * since the rightmost position on the layout (to the right of 'A') is
2158             * selected, the selection is extended to the right of the bounds.
2159             * @param firstEndpoint one end of the visual selection
2160             * @param secondEndpoint the other end of the visual selection
2161             * @param bounds the bounding rectangle to which to extend the selection.
2162             *     This is in baseline-relative coordinates.
2163             * @return a <code>Shape</code> enclosing the selection.  This is in
2164             *     standard coordinates.
2165             * @see #getLogicalRangesForVisualSelection(TextHitInfo, TextHitInfo)
2166             * @see #getLogicalHighlightShape(int, int, Rectangle2D)
2167             */
2168            public Shape getVisualHighlightShape(TextHitInfo firstEndpoint,
2169                    TextHitInfo secondEndpoint, Rectangle2D bounds) {
2170                ensureCache();
2171
2172                checkTextHit(firstEndpoint);
2173                checkTextHit(secondEndpoint);
2174
2175                if (bounds == null) {
2176                    throw new IllegalArgumentException(
2177                            "Null Rectangle2D passed to TextLayout.getVisualHighlightShape()");
2178                }
2179
2180                GeneralPath result = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
2181
2182                int firstCaret = hitToCaret(firstEndpoint);
2183                int secondCaret = hitToCaret(secondEndpoint);
2184
2185                result.append(caretBoundingShape(firstCaret, secondCaret,
2186                        bounds), false);
2187
2188                if (firstCaret == 0 || secondCaret == 0) {
2189                    GeneralPath ls = leftShape(bounds);
2190                    if (!ls.getBounds().isEmpty())
2191                        result.append(ls, false);
2192                }
2193
2194                if (firstCaret == characterCount
2195                        || secondCaret == characterCount) {
2196                    GeneralPath rs = rightShape(bounds);
2197                    if (!rs.getBounds().isEmpty()) {
2198                        result.append(rs, false);
2199                    }
2200                }
2201
2202                LayoutPathImpl lp = textLine.getLayoutPath();
2203                if (lp != null) {
2204                    result = (GeneralPath) lp.mapShape(result); // dlf cast safe?
2205                }
2206
2207                return result;
2208            }
2209
2210            /**
2211             * Returns a <code>Shape</code> enclosing the visual selection in the
2212             * specified range, extended to the bounds.  This method is a
2213             * convenience overload of <code>getVisualHighlightShape</code> that
2214             * uses the natural bounds of this <code>TextLayout</code>.
2215             * @param firstEndpoint one end of the visual selection
2216             * @param secondEndpoint the other end of the visual selection
2217             * @return a <code>Shape</code> enclosing the selection.  This is
2218             *     in standard coordinates.
2219             */
2220            public Shape getVisualHighlightShape(TextHitInfo firstEndpoint,
2221                    TextHitInfo secondEndpoint) {
2222                return getVisualHighlightShape(firstEndpoint, secondEndpoint,
2223                        getNaturalBounds());
2224            }
2225
2226            /**
2227             * Returns a <code>Shape</code> enclosing the logical selection in the
2228             * specified range, extended to the specified <code>bounds</code>.
2229             * <p>
2230             * If the selection range includes the first logical character, the
2231             * selection is extended to the portion of <code>bounds</code> before 
2232             * the start of this <code>TextLayout</code>.  If the range includes
2233             * the last logical character, the selection is extended to the portion
2234             * of <code>bounds</code> after the end of this <code>TextLayout</code>.  
2235             * The height (width on vertical lines) of the selection is always
2236             * extended to <code>bounds</code>.
2237             * <p>
2238             * The selection can be discontiguous on lines with mixed-direction text.
2239             * Only those characters in the logical range between start and limit
2240             * appear selected.  For example, consider the text 'ABCdef' where capital
2241             * letters indicate right-to-left text, rendered on a right-to-left line,
2242             * with a logical selection from 0 to 4 ('ABCd').  The text appears as
2243             * follows, with bold standing in for the selection, and underlining for
2244             * the extension:
2245             * <br><pre>
2246             *    <u><b>d</b></u>ef<u><b>CBA  </b></u>
2247             * </pre>
2248             * The selection is discontiguous because the selected characters are
2249             * visually discontiguous. Also note that since the range includes the
2250             * first logical character (A), the selection is extended to the portion
2251             * of the <code>bounds</code> before the start of the layout, which in
2252             * this case (a right-to-left line) is the right portion of the 
2253             * <code>bounds</code>.
2254             * @param firstEndpoint an endpoint in the range of characters to select
2255             * @param secondEndpoint the other endpoint of the range of characters
2256             * to select. Can be less than <code>firstEndpoint</code>.  The range
2257             * includes the character at min(firstEndpoint, secondEndpoint), but
2258             * excludes max(firstEndpoint, secondEndpoint).
2259             * @param bounds the bounding rectangle to which to extend the selection.
2260             *     This is in baseline-relative coordinates.
2261             * @return an area enclosing the selection.  This is in standard
2262             *     coordinates.
2263             * @see #getVisualHighlightShape(TextHitInfo, TextHitInfo, Rectangle2D)
2264             */
2265            public Shape getLogicalHighlightShape(int firstEndpoint,
2266                    int secondEndpoint, Rectangle2D bounds) {
2267                if (bounds == null) {
2268                    throw new IllegalArgumentException(
2269                            "Null Rectangle2D passed to TextLayout.getLogicalHighlightShape()");
2270                }
2271
2272                ensureCache();
2273
2274                if (firstEndpoint > secondEndpoint) {
2275                    int t = firstEndpoint;
2276                    firstEndpoint = secondEndpoint;
2277                    secondEndpoint = t;
2278                }
2279
2280                if (firstEndpoint < 0 || secondEndpoint > characterCount) {
2281                    throw new IllegalArgumentException(
2282                            "Range is invalid in TextLayout.getLogicalHighlightShape()");
2283                }
2284
2285                GeneralPath result = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
2286
2287                int[] carets = new int[10]; // would this ever not handle all cases?
2288                int count = 0;
2289
2290                if (firstEndpoint < secondEndpoint) {
2291                    int logIndex = firstEndpoint;
2292                    do {
2293                        carets[count++] = hitToCaret(TextHitInfo
2294                                .leading(logIndex));
2295                        boolean ltr = textLine.isCharLTR(logIndex);
2296
2297                        do {
2298                            logIndex++;
2299                        } while (logIndex < secondEndpoint
2300                                && textLine.isCharLTR(logIndex) == ltr);
2301
2302                        int hitCh = logIndex;
2303                        carets[count++] = hitToCaret(TextHitInfo
2304                                .trailing(hitCh - 1));
2305
2306                        if (count == carets.length) {
2307                            int[] temp = new int[carets.length + 10];
2308                            System.arraycopy(carets, 0, temp, 0, count);
2309                            carets = temp;
2310                        }
2311                    } while (logIndex < secondEndpoint);
2312                } else {
2313                    count = 2;
2314                    carets[0] = carets[1] = hitToCaret(TextHitInfo
2315                            .leading(firstEndpoint));
2316                }
2317
2318                // now create paths for pairs of carets
2319
2320                for (int i = 0; i < count; i += 2) {
2321                    result.append(caretBoundingShape(carets[i], carets[i + 1],
2322                            bounds), false);
2323                }
2324
2325                if (firstEndpoint != secondEndpoint) {
2326                    if ((textLine.isDirectionLTR() && firstEndpoint == 0)
2327                            || (!textLine.isDirectionLTR() && secondEndpoint == characterCount)) {
2328                        GeneralPath ls = leftShape(bounds);
2329                        if (!ls.getBounds().isEmpty()) {
2330                            result.append(ls, false);
2331                        }
2332                    }
2333
2334                    if ((textLine.isDirectionLTR() && secondEndpoint == characterCount)
2335                            || (!textLine.isDirectionLTR() && firstEndpoint == 0)) {
2336
2337                        GeneralPath rs = rightShape(bounds);
2338                        if (!rs.getBounds().isEmpty()) {
2339                            result.append(rs, false);
2340                        }
2341                    }
2342                }
2343
2344                LayoutPathImpl lp = textLine.getLayoutPath();
2345                if (lp != null) {
2346                    result = (GeneralPath) lp.mapShape(result); // dlf cast safe?
2347                }
2348                return result;
2349            }
2350
2351            /**
2352             * Returns a <code>Shape</code> enclosing the logical selection in the
2353             * specified range, extended to the natural bounds of this
2354             * <code>TextLayout</code>.  This method is a convenience overload of 
2355             * <code>getLogicalHighlightShape</code> that uses the natural bounds of 
2356             * this <code>TextLayout</code>.
2357             * @param firstEndpoint an endpoint in the range of characters to select
2358             * @param secondEndpoint the other endpoint of the range of characters
2359             * to select. Can be less than <code>firstEndpoint</code>.  The range
2360             * includes the character at min(firstEndpoint, secondEndpoint), but
2361             * excludes max(firstEndpoint, secondEndpoint).
2362             * @return a <code>Shape</code> enclosing the selection.  This is in
2363             *     standard coordinates.
2364             */
2365            public Shape getLogicalHighlightShape(int firstEndpoint,
2366                    int secondEndpoint) {
2367
2368                return getLogicalHighlightShape(firstEndpoint, secondEndpoint,
2369                        getNaturalBounds());
2370            }
2371
2372            /**
2373             * Returns the black box bounds of the characters in the specified range.
2374             * The black box bounds is an area consisting of the union of the bounding
2375             * boxes of all the glyphs corresponding to the characters between start
2376             * and limit.  This area can be disjoint.
2377             * @param firstEndpoint one end of the character range
2378             * @param secondEndpoint the other end of the character range.  Can be
2379             * less than <code>firstEndpoint</code>.
2380             * @return a <code>Shape</code> enclosing the black box bounds.  This is
2381             *     in standard coordinates.
2382             */
2383            public Shape getBlackBoxBounds(int firstEndpoint, int secondEndpoint) {
2384                ensureCache();
2385
2386                if (firstEndpoint > secondEndpoint) {
2387                    int t = firstEndpoint;
2388                    firstEndpoint = secondEndpoint;
2389                    secondEndpoint = t;
2390                }
2391
2392                if (firstEndpoint < 0 || secondEndpoint > characterCount) {
2393                    throw new IllegalArgumentException(
2394                            "Invalid range passed to TextLayout.getBlackBoxBounds()");
2395                }
2396
2397                /*
2398                 * return an area that consists of the bounding boxes of all the
2399                 * characters from firstEndpoint to limit
2400                 */
2401
2402                GeneralPath result = new GeneralPath(GeneralPath.WIND_NON_ZERO);
2403
2404                if (firstEndpoint < characterCount) {
2405                    for (int logIndex = firstEndpoint; logIndex < secondEndpoint; 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(
2416                            dx, dy);
2417                    result = (GeneralPath) tx.createTransformedShape(result);
2418                }
2419                LayoutPathImpl lp = textLine.getLayoutPath();
2420                if (lp != null) {
2421                    result = (GeneralPath) lp.mapShape(result);
2422                }
2423
2424                //return new Highlight(result, false);
2425                return result;
2426            }
2427
2428            /**
2429             * Returns the distance from the point (x,&nbsp;y) to the caret along 
2430             * the line direction defined in <code>caretInfo</code>.  Distance is 
2431             * negative if the point is to the left of the caret on a horizontal 
2432             * line, or above the caret on a vertical line.
2433             * Utility for use by hitTestChar.
2434             */
2435            private float caretToPointDistance(float[] caretInfo, float x,
2436                    float y) {
2437                // distanceOffBaseline is negative if you're 'above' baseline
2438
2439                float lineDistance = isVerticalLine ? y : x;
2440                float distanceOffBaseline = isVerticalLine ? -x : y;
2441
2442                return lineDistance - caretInfo[0]
2443                        + (distanceOffBaseline * caretInfo[1]);
2444            }
2445
2446            /**
2447             * Returns a <code>TextHitInfo</code> corresponding to the 
2448             * specified point.
2449             * Coordinates outside the bounds of the <code>TextLayout</code>
2450             * map to hits on the leading edge of the first logical character, 
2451             * or the trailing edge of the last logical character, as appropriate, 
2452             * regardless of the position of that character in the line.  Only the
2453             * direction along the baseline is used to make this evaluation.
2454             * @param x the x offset from the origin of this 
2455             *     <code>TextLayout</code>.  This is in standard coordinates.
2456             * @param y the y offset from the origin of this
2457             *     <code>TextLayout</code>.  This is in standard coordinates.
2458             * @param bounds the bounds of the <code>TextLayout</code>.  This
2459             *     is in baseline-relative coordinates.
2460             * @return a hit describing the character and edge (leading or trailing)
2461             *     under the specified point.
2462             */
2463            public TextHitInfo hitTestChar(float x, float y, Rectangle2D bounds) {
2464                // check boundary conditions
2465
2466                LayoutPathImpl lp = textLine.getLayoutPath();
2467                boolean prev = false;
2468                if (lp != null) {
2469                    Point2D.Float pt = new Point2D.Float(x, y);
2470                    prev = lp.pointToPath(pt, pt);
2471                    x = pt.x;
2472                    y = pt.y;
2473                }
2474
2475                if (isVertical()) {
2476                    if (y < bounds.getMinY()) {
2477                        return TextHitInfo.leading(0);
2478                    } else if (y >= bounds.getMaxY()) {
2479                        return TextHitInfo.trailing(characterCount - 1);
2480                    }
2481                } else {
2482                    if (x < bounds.getMinX()) {
2483                        return isLeftToRight() ? TextHitInfo.leading(0)
2484                                : TextHitInfo.trailing(characterCount - 1);
2485                    } else if (x >= bounds.getMaxX()) {
2486                        return isLeftToRight() ? TextHitInfo
2487                                .trailing(characterCount - 1) : TextHitInfo
2488                                .leading(0);
2489                    }
2490                }
2491
2492                // revised hit test
2493                // the original seems too complex and fails miserably with italic offsets
2494                // the natural tendency is to move towards the character you want to hit
2495                // so we'll just measure distance to the center of each character's visual
2496                // bounds, pick the closest one, then see which side of the character's
2497                // center line (italic) the point is on.
2498                // this tends to make it easier to hit narrow characters, which can be a
2499                // bit odd if you're visually over an adjacent wide character. this makes
2500                // a difference with bidi, so perhaps i need to revisit this yet again.
2501
2502                double distance = Double.MAX_VALUE;
2503                int index = 0;
2504                int trail = -1;
2505                CoreMetrics lcm = null;
2506                float icx = 0, icy = 0, ia = 0, cy = 0, dya = 0, ydsq = 0;
2507
2508                for (int i = 0; i < characterCount; ++i) {
2509                    if (!textLine.caretAtOffsetIsValid(i)) {
2510                        continue;
2511                    }
2512                    if (trail == -1) {
2513                        trail = i;
2514                    }
2515                    CoreMetrics cm = textLine.getCoreMetricsAt(i);
2516                    if (cm != lcm) {
2517                        lcm = cm;
2518                        // just work around baseline mess for now
2519                        if (cm.baselineIndex == GraphicAttribute.TOP_ALIGNMENT) {
2520                            cy = -(textLine.getMetrics().ascent - cm.ascent)
2521                                    + cm.ssOffset;
2522                        } else if (cm.baselineIndex == GraphicAttribute.BOTTOM_ALIGNMENT) {
2523                            cy = textLine.getMetrics().descent - cm.descent
2524                                    + cm.ssOffset;
2525                        } else {
2526                            cy = cm.effectiveBaselineOffset(baselineOffsets)
2527                                    + cm.ssOffset;
2528                        }
2529                        float dy = (cm.descent - cm.ascent) / 2 - cy;
2530                        dya = dy * cm.italicAngle;
2531                        cy += dy;
2532                        ydsq = (cy - y) * (cy - y);
2533                    }
2534                    float cx = textLine.getCharXPosition(i);
2535                    float ca = textLine.getCharAdvance(i);
2536                    float dx = ca / 2;
2537                    cx += dx - dya;
2538
2539                    // proximity in x (along baseline) is two times as important as proximity in y
2540                    double nd = Math.sqrt(4 * (cx - x) * (cx - x) + ydsq);
2541                    if (nd < distance) {
2542                        distance = nd;
2543                        index = i;
2544                        trail = -1;
2545                        icx = cx;
2546                        icy = cy;
2547                        ia = cm.italicAngle;
2548                    }
2549                }
2550                boolean left = x < icx - (y - icy) * ia;
2551                boolean leading = textLine.isCharLTR(index) == left;
2552                if (trail == -1) {
2553                    trail = characterCount;
2554                }
2555                TextHitInfo result = leading ? TextHitInfo.leading(index)
2556                        : TextHitInfo.trailing(trail - 1);
2557                return result;
2558            }
2559
2560            /**
2561             * Returns a <code>TextHitInfo</code> corresponding to the 
2562             * specified point.  This method is a convenience overload of 
2563             * <code>hitTestChar</code> that uses the natural bounds of this 
2564             * <code>TextLayout</code>.
2565             * @param x the x offset from the origin of this
2566             *     <code>TextLayout</code>.  This is in standard coordinates.
2567             * @param y the y offset from the origin of this
2568             *     <code>TextLayout</code>.  This is in standard coordinates.
2569             * @return a hit describing the character and edge (leading or trailing)
2570             * under the specified point.
2571             */
2572            public TextHitInfo hitTestChar(float x, float y) {
2573
2574                return hitTestChar(x, y, getNaturalBounds());
2575            }
2576
2577            /**
2578             * Returns the hash code of this <code>TextLayout</code>.
2579             * @return the hash code of this <code>TextLayout</code>.
2580             */
2581            public int hashCode() {
2582                if (hashCodeCache == 0) {
2583                    ensureCache();
2584                    hashCodeCache = textLine.hashCode();
2585                }
2586                return hashCodeCache;
2587            }
2588
2589            /**
2590             * Returns <code>true</code> if the specified <code>Object</code> is a 
2591             * <code>TextLayout</code> object and if the specified <code>Object</code>
2592             * equals this <code>TextLayout</code>.
2593             * @param obj an <code>Object</code> to test for equality
2594             * @return <code>true</code> if the specified <code>Object</code>
2595             *      equals this <code>TextLayout</code>; <code>false</code>
2596             *      otherwise.
2597             */
2598            public boolean equals(Object obj) {
2599                return (obj instanceof  TextLayout) && equals((TextLayout) obj);
2600            }
2601
2602            /**
2603             * Returns <code>true</code> if the two layouts are equal.
2604             * Two layouts are equal if they contain equal glyphvectors in the same order.
2605             * @param rhs the <code>TextLayout</code> to compare to this 
2606             *       <code>TextLayout</code>
2607             * @return <code>true</code> if the specified <code>TextLayout</code>
2608             *      equals this <code>TextLayout</code>.
2609             *
2610             */
2611            public boolean equals(TextLayout rhs) {
2612
2613                if (rhs == null) {
2614                    return false;
2615                }
2616                if (rhs == this ) {
2617                    return true;
2618                }
2619
2620                ensureCache();
2621                return textLine.equals(rhs.textLine);
2622            }
2623
2624            /**
2625             * Returns debugging information for this <code>TextLayout</code>.
2626             * @return the <code>textLine</code> of this <code>TextLayout</code>
2627             *        as a <code>String</code>.
2628             */
2629            public String toString() {
2630                ensureCache();
2631                return textLine.toString();
2632            }
2633
2634            /**
2635             * Renders this <code>TextLayout</code> at the specified location in 
2636             * the specified {@link java.awt.Graphics2D Graphics2D} context.
2637             * The origin of the layout is placed at x,&nbsp;y.  Rendering may touch
2638             * any point within <code>getBounds()</code> of this position.  This 
2639             * leaves the <code>g2</code> unchanged.  Text is rendered along the
2640             * baseline path.
2641             * @param g2 the <code>Graphics2D</code> context into which to render
2642             *         the layout
2643             * @param x the X coordinate of the origin of this <code>TextLayout</code>
2644             * @param y the Y coordinate of the origin of this <code>TextLayout</code>
2645             * @see #getBounds()
2646             */
2647            public void draw(Graphics2D g2, float x, float y) {
2648
2649                if (g2 == null) {
2650                    throw new IllegalArgumentException(
2651                            "Null Graphics2D passed to TextLayout.draw()");
2652                }
2653
2654                textLine.draw(g2, x - dx, y - dy);
2655            }
2656
2657            /**
2658             * Package-only method for testing ONLY.  Please don't abuse.
2659             */
2660            TextLine getTextLineForTesting() {
2661
2662                return textLine;
2663            }
2664
2665            /**
2666             *
2667             * Return the index of the first character with a different baseline from the
2668             * character at start, or limit if all characters between start and limit have
2669             * the same baseline.
2670             */
2671            private static int sameBaselineUpTo(Font font, char[] text,
2672                    int start, int limit) {
2673                // current implementation doesn't support multiple baselines
2674                return limit;
2675                /*
2676                byte bl = font.getBaselineFor(text[start++]);
2677                while (start < limit && font.getBaselineFor(text[start]) == bl) {
2678                    ++start;
2679                }
2680                return start;
2681                 */
2682            }
2683
2684            static byte getBaselineFromGraphic(GraphicAttribute graphic) {
2685
2686                byte alignment = (byte) graphic.getAlignment();
2687
2688                if (alignment == GraphicAttribute.BOTTOM_ALIGNMENT
2689                        || alignment == GraphicAttribute.TOP_ALIGNMENT) {
2690
2691                    return (byte) GraphicAttribute.ROMAN_BASELINE;
2692                } else {
2693                    return alignment;
2694                }
2695            }
2696
2697            /**
2698             * Returns a <code>Shape</code> representing the outline of this
2699             * <code>TextLayout</code>.
2700             * @param tx an optional {@link AffineTransform} to apply to the
2701             *     outline of this <code>TextLayout</code>.
2702             * @return a <code>Shape</code> that is the outline of this
2703             *     <code>TextLayout</code>.  This is in standard coordinates.
2704             */
2705            public Shape getOutline(AffineTransform tx) {
2706                ensureCache();
2707                Shape result = textLine.getOutline(tx);
2708                LayoutPathImpl lp = textLine.getLayoutPath();
2709                if (lp != null) {
2710                    result = lp.mapShape(result);
2711                }
2712                return result;
2713            }
2714
2715            /**
2716             * Return the LayoutPath, or null if the layout path is the
2717             * default path (x maps to advance, y maps to offset).
2718             * @return the layout path
2719             * @since 1.6
2720             */
2721            public LayoutPath getLayoutPath() {
2722                return textLine.getLayoutPath();
2723            }
2724
2725            /**
2726             * Convert a hit to a point in standard coordinates.  The point is 
2727             * on the baseline of the character at the leading or trailing
2728             * edge of the character, as appropriate.  If the path is
2729             * broken at the side of the character represented by the hit, the
2730             * point will be adjacent to the character.
2731             * @param hit the hit to check.  This must be a valid hit on
2732             * the TextLayout.
2733             * @param point the returned point. The point is in standard
2734             *     coordinates.
2735             * @throws IllegalArgumentException if the hit is not valid for the
2736             * TextLayout.
2737             * @throws NullPointerException if hit or point is null.
2738             * @since 1.6
2739             */
2740            public void hitToPoint(TextHitInfo hit, Point2D point) {
2741                if (hit == null || point == null) {
2742                    throw new NullPointerException((hit == null ? "hit"
2743                            : "point")
2744                            + " can't be null");
2745                }
2746                ensureCache();
2747                checkTextHit(hit);
2748
2749                float adv = 0;
2750                float off = 0;
2751
2752                int ix = hit.getCharIndex();
2753                boolean leading = hit.isLeadingEdge();
2754                boolean ltr;
2755                if (ix == -1 || ix == textLine.characterCount()) {
2756                    ltr = textLine.isDirectionLTR();
2757                    adv = (ltr == (ix == -1)) ? 0 : lineMetrics.advance;
2758                } else {
2759                    ltr = textLine.isCharLTR(ix);
2760                    adv = textLine.getCharLinePosition(ix, leading);
2761                    off = textLine.getCharYPosition(ix);
2762                }
2763                point.setLocation(adv, off);
2764                LayoutPath lp = textLine.getLayoutPath();
2765                if (lp != null) {
2766                    lp.pathToPoint(point, ltr != leading, point);
2767                }
2768            }
2769        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.