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