home *** CD-ROM | disk | FTP | other *** search
/ Java 1.2 How-To / JavaHowTo.iso / 3rdParty / jbuilder / unsupported / JDK1.2beta3 / SOURCE / SRC.ZIP / java / awt / font / TextLayout.java < prev    next >
Encoding:
Java Source  |  1998-03-20  |  121.2 KB  |  3,450 lines

  1. /*
  2.  * @(#)TextLayout.java    1.38 98/03/18
  3.  *
  4.  * Copyright 1997, 1998 by Sun Microsystems, Inc.,
  5.  * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
  6.  * All rights reserved.
  7.  *
  8.  * This software is the confidential and proprietary information
  9.  * of Sun Microsystems, Inc. ("Confidential Information").  You
  10.  * shall not disclose such Confidential Information and shall use
  11.  * it only in accordance with the terms of the license agreement
  12.  * you entered into with Sun.
  13.  */
  14.  
  15. /*
  16.  * (C) Copyright Taligent, Inc. 1996 - 1997, All Rights Reserved
  17.  * (C) Copyright IBM Corp. 1996 - 1998, All Rights Reserved
  18.  *
  19.  * The original version of this source code and documentation is
  20.  * copyrighted and owned by Taligent, Inc., a wholly-owned subsidiary
  21.  * of IBM. These materials are provided under terms of a License
  22.  * Agreement between Taligent and Sun. This technology is protected
  23.  * by multiple US and International patents.
  24.  *
  25.  * This notice and attribution to Taligent may not be removed.
  26.  * Taligent is a registered trademark of Taligent, Inc.
  27.  *
  28.  */
  29.  
  30. package java.awt.font;
  31.  
  32. import java.awt.Color;
  33. import java.awt.Font;
  34. import java.awt.Graphics2D;
  35. import java.awt.Shape;
  36. import java.awt.geom.AffineTransform;
  37. import java.awt.geom.GeneralPath;
  38. import java.awt.geom.Point2D;
  39. import java.awt.geom.Rectangle2D;
  40. import java.text.AttributedCharacterIterator;
  41. import java.text.AttributeSet;
  42.  
  43. /**
  44.  * TextLayout is an immutable graphical representation of styled character data.
  45.  * <p>
  46.  * It provides the following capabilities:<ul>
  47.  * <li>implicit bidirectional analysis and reordering,
  48.  * <li>cursor positioning and movement, including split cursors for
  49.  * mixed directional text,
  50.  * <li>highlighting, including both logical and visual highlighting
  51.  * for mixed directional text,
  52.  * <li>multiple baselines (roman, hanging, and centered),
  53.  * <li>hit testing,
  54.  * <li>justification,
  55.  * <li>default font substitution,
  56.  * <li>metric information such as ascent, descent, and advance, and
  57.  * <li>rendering
  58.  * </ul>
  59.  * <p>
  60.  * TextLayout can be rendered by calling Graphics2D.drawString passing
  61.  * an instance of TextLayout and a position as the arguments.
  62.  * <p>
  63.  * TextLayout can be constructed either directly or through use of a
  64.  * LineBreakMeasurer.  When constructed directly, the source text represents
  65.  * a single paragraph.  LineBreakMeasurer provides support for line breaking
  66.  * to support wrapped text, see its documentation for more information.
  67.  * <p>
  68.  * TextLayout construction logically proceeds as follows:<ul>
  69.  * <li>paragraph attributes are extracted and examined,
  70.  * <li>text is analyzed for bidirectional reordering, and reordering
  71.  * information is computed if needed,
  72.  * <li>text is segmented into style runs
  73.  * <li>fonts are chosen for style runs, first by using a font if the
  74.  * attribute TextAttributeSet.FONT is present, otherwise by computing
  75.  * a default font using the attributes that have been defined
  76.  * <li>if a default font was computed, the style runs are further
  77.  * broken into subruns renderable by the font, so that font substitution
  78.  * appropriate to the text can be performed,
  79.  * <li>if text is on multiple baselines, the runs or subruns are further
  80.  * broken into subruns sharing a common baseline,
  81.  * <li>glyphsets are generated for each run using the chosen font,
  82.  * <li>final bidirectional reordering is performed on the glyphsets
  83.  * </ul>
  84.  * <p>
  85.  * All graphical information returned from TextLayout's methods is relative
  86.  * to the origin of the TextLayout, which is the intersection of the
  87.  * TextLayout's baseline with its left edge.  Also, coordinates passed
  88.  * into TextLayout's methods are assumed to be relative to the TextLayout's
  89.  * origin.  Clients will usually need to translate between TextLayout's
  90.  * coordinate system and the coordinate system in another object (such as
  91.  * a Graphics).
  92.  * <p>
  93.  * TextLayouts are constructed from styled text, but they do not retain a
  94.  * reference to their source text.  Thus, changes in the text previously used
  95.  * to generate a TextLayout do not affect the TextLayout.
  96.  * <p>
  97.  * Three methods on TextLayout (<code>getNextRightHit</code>,
  98.  * <code>getNextLeftHit</code>, and <code>hitTestChar</code>) return instances
  99.  * of <code>TextHitInfo</code>.  The offsets contained in these TextHitInfo's
  100.  * are relative to the start of the TextLayout, <b>not</b> to the text used to
  101.  * create the TextLayout.  Similarly, TextLayout methods which accept
  102.  * TextHitInfo instances as parameters expect the TextHitInfo's offsets to be
  103.  * relative to the TextLayout, not to any underlying text storage model.
  104.  * <p>
  105.  * <strong>Examples</strong>:<p>
  106.  * Constructing and drawing a TextLayout and its bounding rectangle:
  107.  * <blockquote><pre>
  108.  *   Graphics2D g = ...;
  109.  *   Point2D loc = ...;
  110.  *   Font font = Font.getFont("Helvetica-bold-italic");
  111.  *   TextLayout layout = new TextLayout("This is a string", font);
  112.  *   g.drawString(layout, loc.getX(), loc.getY());
  113.  *
  114.  *   Rectangle2D bounds = layout.getBounds();
  115.  *   bounds.setRect(bounds.getX()+loc.getX(),
  116.  *                  bounds.getY()+loc.getY(),
  117.  *                  bounds.getWidth(),
  118.  *                  bounds.getHeight())
  119.  *   g.draw(bounds);
  120.  * </pre>
  121.  * </blockquote>
  122.  * <p>
  123.  * Hit-testing a TextLayout (determining which character is at
  124.  * a particular graphical location):
  125.  * <blockquote><pre>
  126.  *   Point2D click = ...;
  127.  *   TextHitInfo hit = layout.hitTestChar(
  128.  *                         (float) (click.getX() - loc.getX()),
  129.  *                         (float) (click.getY() - loc.getY()));
  130.  * </pre>
  131.  * </blockquote>
  132.  * <p>
  133.  * Displaying every caret position in a layout.  Also, this demonstrates
  134.  * how to correctly right-arrow from one TextHitInfo to another:
  135.  * <blockquoute><pre>
  136.  *   // translate graphics to origin of layout on screen
  137.  *   g.translate(loc.getX(), loc.getY());
  138.  *   TextHitInfo hit = layout.isLeftToRight()?
  139.  *              TextHitInfo.trailing(-1) :
  140.  *              TextHitInfo.leading(layout.getCharacterCount());
  141.  *   for (; hit != null; hit = layout.getNextRightHit(hit.getInsertionOffset())) {
  142.  *       Shape[] carets = layout.getCaretShapes(hit.getInsertionOffset());
  143.  *       Shape caret = carets[0] == null? carets[1] : carets[0];
  144.  *       g.draw(caret);
  145.  *   }
  146.  * </pre></blockquote>
  147.  * <p>
  148.  * Drawing a selection range corresponding to a substring in the source text.
  149.  * The selected area may not be visually contiguous:
  150.  * <blockquoute><pre>
  151.  *   // selStart, selLimit should be relative to the layout,
  152.  *   // not to the source text
  153.  *
  154.  *   int selStart = ..., selLimit = ...;
  155.  *   Color selectionColor = ...;
  156.  *   Shape selection = layout.getLogicalHighlight(selStart, selLimit);
  157.  *   // selection may consist of disjoint areas
  158.  *   // graphics is assumed to be tranlated to origin of layout
  159.  *   g.setColor(selectionColor);
  160.  *   g.fill(selection);
  161.  * </pre></blockquote>
  162.  * <p>
  163.  * Drawing a visually contiguous selection range.  The selection range may
  164.  * correspond to more than one substring in the source text.  The ranges of
  165.  * the corresponding source text substrings can be obtained with
  166.  * <code>getLogicalRangesForVisualSelection()</code>:
  167.  * <blockquoute><pre>
  168.  *   TextOffset selStart = ..., selLimit = ...;
  169.  *   Shape selection = layout.getVisualHighlightSelection(selStart, selLimit);
  170.  *   g.setColor(selectionColor);
  171.  *   g.fill(selection);
  172.  *   int[] ranges = getLogicalRangesForVisualSelection(selStart, selLimit);
  173.  *   // ranges[0], ranges[1] is the first selection range,
  174.  *   // ranges[2], ranges[3] is the second selection range, etc.
  175.  * </pre></blockquote>
  176.  * <p>
  177.  * @see LineBreakMeasurer
  178.  * @see TextAttributeSet
  179.  * @see TextHitInfo
  180.  */
  181. public final class TextLayout implements Cloneable {
  182.  
  183.     private TextLayoutComponent[] glyphs;
  184.     private int[] glyphsOrder;
  185.     private byte baseline;
  186.     private float[] baselineOffsets;
  187.     private boolean isDirectionLTR;
  188.     private boolean isVerticalLine;
  189.  
  190.     // cached values computed from GlyphSets and set info:
  191.     // all are recomputed from scratch in buildCache()
  192.     private float visibleAdvance;
  193.     private float advance;
  194.     private float ascent;
  195.     private float descent;
  196.     private float leading;
  197.     private int characterCount;
  198.     private int hashCodeCache;
  199.  
  200.     // This value is obtained from an attribute, and constrained to the
  201.     // interval [0,1].  If 0, the layout cannot be justified.
  202.     private float justifyRatio;
  203.  
  204.     // If a layout is produced by justification, then that layout
  205.     // cannot be justified.  To enforce this constraint the
  206.     // justifyRatio of the justified layout is set to this value.
  207.     private static final float ALREADY_JUSTIFIED = -53.9f;
  208.  
  209.     // dx and dy specify the distance between the TextLayout's origin
  210.     // and the origin of the leftmost GlyphSet (TextLayoutComponent,
  211.     // actually).  They were used for hanging punctuation support,
  212.     // which is no longer implemented.  Currently they are both always 0,
  213.     // and TextLayout is not guaranteed to work with non-zero dx, dy
  214.     // values right now.  They were left in as an aide and reminder to
  215.     // anyone who implements hanging punctuation or other similar stuff.
  216.     // They are static now so they don't take up space in TextLayout
  217.     // instances.
  218.     private static float dx;
  219.     private static float dy;
  220.  
  221.     /*
  222.      * TextLayouts are supposedly immutable.  If you mutate a TextLayout under
  223.      * the covers (like the justification code does) you'll need to set this
  224.      * back to null.
  225.      */
  226.     private GlyphIterator protoIterator = null;
  227.  
  228.     /*
  229.      * Natural bounds is used internally.  It is built on demand in
  230.      * getNaturalBounds.
  231.      */
  232.     private Rectangle2D naturalBounds = null;
  233.  
  234.     /*
  235.      * boundsRect encloses all of the bits this TextLayout can draw.  It
  236.      * is build on demand in getBounds.
  237.      */
  238.     private Rectangle2D boundsRect = null;
  239.  
  240.     /*
  241.      * flag to supress/allow carets inside of ligatures when hit testing or
  242.      * arrow-keying
  243.      */
  244.     private boolean caretsInLigaturesAreAllowed = false;
  245.  
  246.     /**
  247.      * This class contains one method, getStrongCaret, which is used to
  248.      * specify the policy which determines the strong caret in dual-caret
  249.      * text.  The strong caret is used to move the caret to the left or
  250.      * right. Instances of this class can be passed to getCarets,
  251.      * getNextLeftHit and getNextRightHit to customize strong caret
  252.      * selection.
  253.      *
  254.      * To specify alternate caret policies, subclass CaretPolicy and
  255.      * override getStrongCaret.  getStrongCaret should inspect the two
  256.      * TextHitInfo arguments and choose one of them as the strong caret.
  257.      *
  258.      * Most clients clients do not need to use this class.
  259.      */
  260.     public static class CaretPolicy {
  261.  
  262.         /**
  263.          * Choose one of the given TextHitInfo instances as a strong
  264.          * caret in the given TextLayout.
  265.          * @param hit1 A valid hit in <code>layout</code>
  266.          * @param hit2 A valid hit in <code>layout</code>
  267.          * @param layout The TextLayout in which <code>hit1</code>
  268.          *        and <code>hit2</code> are used
  269.          * @return either <code>hit1</code> or <code>hit2</code>
  270.          *        (or an equivalent TextHitInfo), indicating the
  271.          *        strong caret
  272.          */
  273.         public TextHitInfo getStrongCaret(TextHitInfo hit1,
  274.                                           TextHitInfo hit2,
  275.                                           TextLayout layout) {
  276.  
  277.             // default implmentation just calls private method on layout
  278.             return layout.getStrongHit(hit1, hit2);
  279.         }
  280.     }
  281.  
  282.     /**
  283.      * This CaretPolicy is used when a policy is not specified by the
  284.      * client.  With this policy, a hit on a character whose direction
  285.      * is the same as the line direction is strong than a hit on a
  286.      * counterdirectional character.  If the character's direcitons are
  287.      * the same, a hit on the leading edge of a character is stronger
  288.      * than a hit on the trailing edge of a character.
  289.      */
  290.     public static final CaretPolicy DEFAULT_CARET_POLICY = new CaretPolicy();
  291.  
  292.     /**
  293.      * Construct a layout from a string and a font.
  294.      *
  295.      * All the text is styled using the provided font.
  296.      *
  297.      * The string must specify a single paragraph of text, as an
  298.      * entire paragraph is required for the bidirectional
  299.      * algorithm.
  300.      *
  301.      * @param str the text to display.
  302.      * @param font a font used to style the text.
  303.      */
  304.     public TextLayout(String string, Font font) {
  305.  
  306.         if (font == null) {
  307.             throw new IllegalArgumentException("Null font passed to TextLayout constructor.");
  308.         }
  309.  
  310.         if (string == null) {
  311.             throw new IllegalArgumentException("Null string passed to TextLayout constructor.");
  312.         }
  313.  
  314.         if (string.length() == 0) {
  315.             throw new IllegalArgumentException("Zero length string passed to TextLayout constructor.");
  316.         }
  317.  
  318.         char[] text = string.toCharArray();
  319.         if (font.sameBaselineUpTo(text, 0, text.length) == text.length) {
  320.             fastInit(text, 0, text.length, font, null);
  321.         } else {
  322.             StyledString ss = new StyledString(string, font);
  323.             standardInit(new StyledStringIterator(ss));
  324.         }
  325.     }
  326.  
  327.     /**
  328.      * Construct a layout from a string and an attribute set.
  329.      * <p>
  330.      * All the text is styled using the provided attributes.
  331.      * <p>
  332.      * The string must specify a single paragraph of text, as an
  333.      * entire paragraph is required for the bidirectional
  334.      * algorithm.
  335.      *
  336.      * @param str the text to display.
  337.      * @param attributes the attributes used to style the text.
  338.      */
  339.     public TextLayout(String string, AttributeSet attributes) {
  340.  
  341.         if (string == null) {
  342.             throw new IllegalArgumentException("Null string passed to TextLayout constructor.");
  343.         }
  344.  
  345.         if (attributes == null) {
  346.             throw new IllegalArgumentException("Null attribute set passed to TextLayout constructor.");
  347.         }
  348.  
  349.         if (string.length() == 0) {
  350.             throw new IllegalArgumentException("Zero length string passed to TextLayout constructor.");
  351.         }
  352.  
  353.         char[] text = string.toCharArray();
  354.         Font font = singleFont(text, 0, text.length, attributes);
  355.         if (font != null) {
  356.             fastInit(text, 0, text.length, font, attributes);
  357.         } else {
  358.             standardInit(new StyledStringIterator(string, attributes));
  359.         }
  360.     }
  361.  
  362.     /*
  363.      * Determine a font for the attributes, and if a single font can render all
  364.      * the text on one baseline, return it, otherwise null.  If the attributes
  365.      * specify a font, assume it can display all the text without checking.
  366.      *
  367.      * If the AttributeSet contains an embedded graphic, return null.
  368.      */
  369.     private static Font singleFont(char[] text,
  370.                                    int start,
  371.                                    int limit,
  372.                                    AttributeSet attributes) {
  373.  
  374.         if (attributes.get(TextAttributeSet.EMBEDDED_GRAPHIC) != null) {
  375.             return null;
  376.         }
  377.  
  378.         Font font = (Font)attributes.get(TextAttributeSet.FONT);
  379.         if (font == null) {
  380.                 font = Font.getFont(attributes); // uses static cache in Font;
  381.                 if (font.canDisplayUpTo(text, start, limit) != limit) {
  382.                     return null;
  383.                 }
  384.             }
  385.  
  386.         if (font.sameBaselineUpTo(text, start, limit) != limit) {
  387.             return null;
  388.         }
  389.  
  390.         return font;
  391.     }
  392.  
  393.     /**
  394.      * Construct a layout from a styled string.
  395.      * <p>
  396.      * The text must specify a single paragraph of text, as an
  397.      * entire paragraph is required for the bidirectional
  398.      * algorithm.
  399.      *
  400.      * @param text the styled text to display.
  401.      */
  402.     public TextLayout(StyledString text) {
  403.  
  404.         if (text == null) {
  405.             throw new IllegalArgumentException("Null styled string passed to TextLayout constructor.");
  406.         }
  407.  
  408.         if (text.length() == 0) {
  409.             throw new IllegalArgumentException("Zero length styled string passed to TextLayout constructor.");
  410.         }
  411.  
  412.             // direct access to StyledString internal data!!!
  413.         if (text.attrs.length == 1) {
  414.             AttributeSet attrs = text.attrs[0];
  415.             Font font = singleFont(text.chars, 0, text.chars.length, attrs);
  416.             if (font != null) {
  417.                 fastInit(text.chars, 0, text.chars.length, font, attrs);
  418.                 return;
  419.             }
  420.         }
  421.         standardInit(new StyledStringIterator(text));
  422.     }
  423.  
  424.     /**
  425.      * Construct a layout from an iterator over styled text.
  426.      * <p>
  427.      * The iterator must specify a single paragraph of text, as an
  428.      * entire paragraph is required for the bidirectional
  429.      * algorithm.
  430.      *
  431.      * @param text the styled text to display.
  432.      */
  433.     public TextLayout(AttributedCharacterIterator text) {
  434.  
  435.         if (text == null) {
  436.             throw new IllegalArgumentException("Null iterator passed to TextLayout constructor.");
  437.         }
  438.  
  439.             int start = text.getBeginIndex();
  440.             int limit = text.getEndIndex();
  441.         if (start == limit) {
  442.             throw new IllegalArgumentException("Zero length iterator passed to TextLayout constructor.");
  443.         }
  444.  
  445.         text.first();
  446.         if (text.getRunLimit() == limit) {
  447.             int len = limit - start;
  448.             char[] chars = new char[len];
  449.             // text.getChars(start, limit, chars, 0);
  450.             int n = 0;
  451.             for (char c = text.first(); c != text.DONE; c = text.next()) {
  452.                 chars[n++] = c;
  453.             }
  454.  
  455.             text.first();
  456.             AttributeSet attributes = text.getAttributes();
  457.             Font font = singleFont(chars, 0, len, attributes);
  458.             if (font != null) {
  459.                     fastInit(chars, 0, len, font, attributes);
  460.                     return;
  461.             }
  462.         }
  463.  
  464.         standardInit(text);
  465.     }
  466.  
  467.     /**
  468.      * Initialize the paragraph-specific data.
  469.      */
  470.     private void paragraphInit(byte aBaseline, float[] aBaselineOffsets, AttributeSet attrs) {
  471.         // Object vf = attrs.get(TextAttributeSet.ORIENTATION);
  472.         // isVerticalLine = TextAttributeSet.ORIENTATION_VERTICAL.equals(vf);
  473.         isVerticalLine = false;
  474.  
  475.         /*
  476.          * if paragraph features define a baseline, use it, otherwise use
  477.          * the baseline for the first char.
  478.          */
  479.         Byte baselineLF = (Byte)attrs.get(TextAttributeSet.BASELINE);
  480.         if (baselineLF == null) {
  481.             baseline = aBaseline;
  482.         } else {
  483.             baseline = baselineLF.byteValue();
  484.         }
  485.  
  486.         /*
  487.          * if paragraph features define the baselines, use them, otherwise
  488.          * use the baselines for the first font that can display the initial
  489.          * text.
  490.          */
  491.         baselineOffsets = (float[])attrs.get(TextAttributeSet.BASELINE_OFFSETS);
  492.         if (baselineOffsets == null) {
  493.             baselineOffsets = aBaselineOffsets;
  494.         }
  495.  
  496.         // normalize to current baseline
  497.         if (baselineOffsets[baseline] != 0) {
  498.             float base = baselineOffsets[baseline];
  499.             float[] temp = new float[baselineOffsets.length];
  500.             for (int i = 0; i < temp.length; i++)
  501.                 temp[i] = baselineOffsets[i] - base;
  502.             baselineOffsets = temp;
  503.         }
  504.  
  505.         /*
  506.          * if justification is defined, use it (forcing to valid range)
  507.          * otherwise set to 1.0
  508.          */
  509.         Float justifyLF = (Float) attrs.get(TextAttributeSet.JUSTIFICATION);
  510.         if (justifyLF == null) {
  511.             justifyRatio = 1;
  512.         } else {
  513.             justifyRatio = justifyLF.floatValue();
  514.  
  515.             if (justifyRatio < 0) {
  516.                 justifyRatio = 0;
  517.             } else if (justifyRatio > 1) {
  518.                 justifyRatio = 1;
  519.             }
  520.         }
  521.     }
  522.  
  523.     /*
  524.      * the fast init generates a single glyph set.  This requires:
  525.      * all one style
  526.      * all renderable by one font (ie no embedded graphics)
  527.      * all on one baseline
  528.      */
  529.     private void fastInit(char[] text, int start, int limit, Font font, AttributeSet attrs) {
  530.         // Object vf = attrs.get(TextAttributeSet.ORIENTATION);
  531.         // isVerticalLine = TextAttributeSet.ORIENTATION_VERTICAL.equals(vf);
  532.         isVerticalLine = false;
  533.         char c = text[start];
  534.         byte glyphBaseline = font.getBaselineFor(c);
  535.         if (attrs == null) {
  536.             baseline = glyphBaseline;
  537.             baselineOffsets = font.getBaselineOffsetsFor(c);
  538.             justifyRatio = 1.0f;
  539.         } else {
  540.             char ch = text[start];
  541.             paragraphInit(font.getBaselineFor(ch), font.getBaselineOffsetsFor(ch), attrs);
  542.         }
  543.  
  544.         byte[] levels = null;
  545.         int[] inverse = null;
  546.         IncrementalBidi bidi = IncrementalBidi.createBidi(text, start, limit, attrs);
  547.         if (bidi != null) {
  548.             isDirectionLTR = bidi.isDirectionLTR();
  549.             levels = bidi.getLevels();
  550.             inverse = bidi.createLogicalToVisualMap();
  551.         } else {
  552.             isDirectionLTR = true;
  553.         }
  554.         glyphs = new TextLayoutComponent[1];
  555.         GlyphSet gs = font.getGlyphSet(text, start, limit, glyphBaseline, inverse, levels);
  556.         glyphs[0] = new GlyphSetComponent(gs);
  557.     }
  558.  
  559.     /*
  560.      * the standard init generates multiple glyph sets based on style,
  561.      * renderable, and baseline runs.
  562.      */
  563.     private void standardInit(AttributedCharacterIterator text) {
  564.  
  565.         // set paragraph attributes
  566.         {
  567.             // If there's an embedded graphic at the start of the
  568.             // paragraph, look for the first non-graphic character
  569.             // and use it and its font to initialize the paragraph.
  570.             // If not, use the first graphic to initialize.
  571.  
  572.             char firstChar = text.first();
  573.             AttributeSet paragraphAttrs = text.getAttributes();
  574.             AttributeSet fontAttrs = paragraphAttrs;
  575.             GraphicAttribute firstGraphic = (GraphicAttribute)
  576.                         paragraphAttrs.get(TextAttributeSet.EMBEDDED_GRAPHIC);
  577.  
  578.             boolean useFirstGraphic = false;
  579.  
  580.             if (firstGraphic != null) {
  581.  
  582.                 useFirstGraphic = true;
  583.  
  584.                 for (firstChar = text.setIndex(text.getRunLimit());
  585.                         firstChar != text.DONE;
  586.                         firstChar = text.setIndex(text.getRunLimit())) {
  587.  
  588.                     fontAttrs = text.getAttributes();
  589.                     if (fontAttrs.get(TextAttributeSet.EMBEDDED_GRAPHIC) == null) {
  590.                         useFirstGraphic = false;
  591.                         break;
  592.                     }
  593.                 }
  594.                 if (useFirstGraphic) {
  595.                     firstChar = text.first();
  596.                 }
  597.             }
  598.  
  599.             if (useFirstGraphic) {
  600.                 // hmmm what to do here?  Just try to supply reasonable
  601.                 // values I guess.
  602.  
  603.                 byte defaultBaseline =
  604.                         TextLayoutGraphic.getBaselineFromGraphic(firstGraphic);
  605.                 float[] defaultOffsets =
  606.                                     Font.DEFAULT.getBaselineOffsetsFor(firstChar);
  607.  
  608.                 paragraphInit(defaultBaseline, defaultOffsets, paragraphAttrs);
  609.             }
  610.             else {
  611.                 Font defaultFont = (Font)fontAttrs.get(TextAttributeSet.FONT);
  612.                 if (defaultFont == null) {
  613.                     defaultFont = Font.getBestFontFor(text, text.getIndex(), text.getEndIndex());
  614.                 }
  615.                 paragraphInit(defaultFont.getBaselineFor(firstChar),
  616.                     defaultFont.getBaselineOffsetsFor(firstChar), paragraphAttrs);
  617.             }
  618.         }
  619.  
  620.         isDirectionLTR = true;
  621.         byte[] levels = null;
  622.         int[] inverse = null;
  623.  
  624.         IncrementalBidi bidi = IncrementalBidi.createBidi(text);
  625.         if (bidi != null) {
  626.             isDirectionLTR = bidi.isDirectionLTR();
  627.             levels = bidi.getLevels();
  628.             inverse = bidi.createLogicalToVisualMap();
  629.         }
  630.  
  631.         int textStart = text.getBeginIndex();
  632.         int textLimit = text.getEndIndex();
  633.         characterCount = textLimit - textStart;
  634.  
  635.         /*
  636.          * create a vector to temporarily hold glyph sets.  We don't create the
  637.          * array  yet because we don't know how long it should be.  Some style
  638.          * runs may be broken up into several different glyph sets because a
  639.          * font can't display the entire run or because it is visually
  640.          * discontiguous.
  641.          */
  642.  
  643.         java.util.Vector glyphVector = new java.util.Vector();
  644.  
  645.         /*
  646.          * text may be inside some larger text, be sure to adjust before
  647.          * accessing arrays, which map zero to the start of the text.
  648.          */
  649.         int pos = textStart;
  650.         do {
  651.             text.setIndex(pos);
  652.             int runLimit = text.getRunLimit(); // <= textLimit
  653.  
  654.             AttributeSet attributes = text.getAttributes();
  655.             GraphicAttribute graphicAttribute = (GraphicAttribute)
  656.                         attributes.get(TextAttributeSet.EMBEDDED_GRAPHIC);
  657.  
  658.             if (graphicAttribute != null) {
  659.  
  660.                 do {
  661.                     int chunkLimit =
  662.                         textStart + firstVisualChunk(inverse, levels,
  663.                                     pos - textStart, runLimit - textStart);
  664.                     TextLayoutGraphic graphic = new TextLayoutGraphic(
  665.                                     text.getBeginIndex(), graphicAttribute,
  666.                                     pos, chunkLimit, inverse, levels);
  667.                     glyphVector.addElement(graphic);
  668.                     pos = chunkLimit;
  669.                 } while(pos < runLimit);
  670.             }
  671.             else {
  672.                 do {
  673.                     /*
  674.                      * If the client has indicated a font, they're responsible for
  675.                      * ensuring that it can display all the text to which it is
  676.                      * applied.  We won't do anything to handle it.
  677.                      */
  678.                     int displayLimit = runLimit; // default
  679.  
  680.                         Font font = (Font) attributes.get(TextAttributeSet.FONT);
  681.  
  682.                     if (font == null) {
  683.                         font = Font.getBestFontFor(text, pos, runLimit);
  684.                         displayLimit = font.canDisplayUpTo(text, pos, runLimit);
  685.                     }
  686.  
  687.                         do {
  688.                             int chunkLimit = textStart + firstVisualChunk(inverse,
  689.                                  levels, pos - textStart,
  690.                                  displayLimit - textStart); // <= displayLimit
  691.  
  692.                         do {
  693.                             char c = text.setIndex(pos);
  694.                             byte baseline = font.getBaselineFor(c);
  695.                             if (!font.isUniformBaseline()) {
  696.                                 do {
  697.                                     c = text.next();
  698.                                 } while (text.getIndex() < chunkLimit &&
  699.                                              font.getBaselineFor(c) == baseline);
  700.                             } else {
  701.                                 text.setIndex(chunkLimit);
  702.                             }
  703.  
  704.                             GlyphSet glyphSet = font.getGlyphSet(text,
  705.                                 pos, text.getIndex(), baseline, inverse, levels);
  706.  
  707.                             glyphVector.addElement(new GlyphSetComponent(glyphSet));
  708.  
  709.                                 pos = text.getIndex();
  710.  
  711.                         } while (pos < chunkLimit);
  712.  
  713.                     } while (pos < displayLimit);
  714.  
  715.                 } while (pos < runLimit);
  716.             }
  717.  
  718.         } while (pos < textLimit);
  719.  
  720.         glyphs = new TextLayoutComponent[glyphVector.size()];
  721.         for (int i = 0; i < glyphs.length; i++) {
  722.             glyphs[i] = (TextLayoutComponent) glyphVector.elementAt(i);
  723.         }
  724.  
  725.         /*
  726.          * Create a visual ordering for the glyph sets.  The important thing
  727.          * here is that the values have the proper rank with respect to
  728.          * each other, not the exact values.  For example, the first glyph
  729.          * set that appears visually should have the lowest value.  The last
  730.          * should have the highest value.  The values are then normalized
  731.          * to map 1-1 with positions in glyphs.  This is exactly analogous to
  732.          * the way glyphs are maintained in a glyphset.
  733.          *
  734.          * This leaves the array null if the order is canonical.
  735.          */
  736.         if (inverse != null && glyphs.length > 1) {
  737.             glyphsOrder = new int[glyphs.length];
  738.             int start = 0;
  739.             for (int i = 0; i < glyphs.length; i++) {
  740.                 glyphsOrder[i] = inverse[start];
  741.                 start += glyphs[i].getNumGlyphs();
  742.             }
  743.  
  744.             glyphsOrder = GlyphSet.getContiguousOrder(glyphsOrder);
  745.             glyphsOrder = GlyphSet.getInverseOrder(glyphsOrder);
  746.         }
  747.     }
  748.  
  749.     /*
  750.      * A utility to get a range of text that is both logically and visually
  751.      * contiguous.
  752.      * If the entire range is ok, return limit, otherwise return the first
  753.      * directional change after start.  We could do better than this, but
  754.      * it doesn't seem worth it at the moment.
  755.      */
  756.     private static int firstVisualChunk(int order[], byte direction[],
  757.                                         int start, int limit)
  758.     {
  759.         if (order != null) {
  760.             int min = order[start];
  761.             int max = order[start];
  762.             int count = limit - start;
  763.             for (int i = start + 1; i < limit; i++) {
  764.                 min = Math.min(min, order[i]);
  765.                 max = Math.max(max, order[i]);
  766.                 if (max - min >= count) {
  767.                     if (direction != null) {
  768.                         byte baseLevel = direction[start];
  769.                         for (int j = start + 1; j < i; j++) {
  770.                             if (direction[j] != baseLevel) {
  771.                                 return j;
  772.                             }
  773.                         }
  774.                     }
  775.                     return i;
  776.                 }
  777.             }
  778.         }
  779.         return limit;
  780.     }
  781.  
  782.     /*
  783.      * A utility to rebuild the ascent/descent/leading/advance cache.
  784.      * You'll need to call this if you clone and mutate (like justification,
  785.      * editing methods do)
  786.      */
  787.     private void ensureCache() {
  788.         if (protoIterator == null) {
  789.             buildCache();
  790.         }
  791.     }
  792.  
  793.     /**
  794.      * This method is here to keep the Symantec JIT happy.  The code
  795.      * was in buildCache and the JIT didn't like it for some reason.
  796.      */
  797.     private void computeAscent() {
  798.  
  799.         TextLayoutComponent gs;
  800.  
  801.         for (int i = 0; i < glyphs.length; i++) {
  802.             gs = glyphs[i];
  803.             float baselineOffset = baselineOffsets[gs.getBaseline()];
  804.             ascent = Math.max(ascent, -baselineOffset + gs.getAscent());
  805.         }
  806.     }
  807.  
  808.     private void buildCache() {
  809.  
  810.         TextLayoutComponent gs;
  811.         ascent = 0;
  812.         descent = 0;
  813.         leading = 0;
  814.         advance = -dx;
  815.  
  816.         // {jbr} have to do two passes now;  one for ascent and one for descent
  817.         computeAscent();
  818.  
  819.         for (int i = 0; i < glyphs.length; i++) {
  820.             gs = glyphs[i];
  821.             float baselineOffset = baselineOffsets[gs.getBaseline()];
  822.             float gd = baselineOffset + gs.getDescent(ascent+baselineOffset);
  823.             descent = Math.max(descent, gd);
  824.             leading = Math.max(leading, gd + gs.getLeading());
  825.         }
  826.  
  827.         leading -= descent;
  828.  
  829.         GlyphIterator iter = createGlyphIterator();
  830.  
  831.         characterCount = iter.limit();
  832.  
  833.         advance += iter.totalAdvance();
  834.  
  835.         // compute visibleAdvance
  836.         visibleAdvance = advance;
  837.  
  838.         if (isDirectionLTR) {
  839.             iter.lastVisualGlyph();
  840.             while (iter.isValid() && iter.isWhitespace()) {
  841.                 visibleAdvance -= iter.distanceToNextGlyph();
  842.                 iter.previousVisualGlyph();
  843.             }
  844.         } else {
  845.             // by default, iter is set to first visual glyph when created
  846.             while (iter.isValid() && iter.isWhitespace()) {
  847.                 visibleAdvance -= iter.distanceToNextGlyph();
  848.                 iter.nextVisualGlyph();
  849.             }
  850.         }
  851.  
  852.         // naturalBounds, boundsRect will be generated on demand
  853.         naturalBounds = null;
  854.         boundsRect = null;
  855.  
  856.         // hashCode will be regenerated on demand
  857.         hashCodeCache = 0;
  858.     }
  859.  
  860.     private Rectangle2D getNaturalBounds() {
  861.  
  862.         ensureCache();
  863.  
  864.         if (naturalBounds == null) {
  865.  
  866.             TextLayoutComponent gs;
  867.  
  868.             gs = glyphsOrder==null? glyphs[0] : glyphs[glyphsOrder[0]];
  869.             float angle = gs.getItalicAngle();
  870.  
  871.             float leftOrTop = isVerticalLine? -dy : -dx;
  872.             if (angle < 0) {
  873.                 leftOrTop -= angle*gs.getAscent();
  874.             }
  875.             else if (angle > 0) {
  876.                 leftOrTop -= angle*gs.getDescent(ascent);
  877.             }
  878.  
  879.             gs = glyphsOrder==null? glyphs[glyphs.length-1] :
  880.                                     glyphs[glyphsOrder[glyphs.length-1]];
  881.             angle = gs.getItalicAngle();
  882.  
  883.             float rightOrBottom = advance;
  884.             if (angle < 0) {
  885.                 rightOrBottom += angle*gs.getDescent(ascent);
  886.             }
  887.             else {
  888.                 rightOrBottom += angle*gs.getAscent();
  889.             }
  890.  
  891.             float lineDim = rightOrBottom - leftOrTop;
  892.  
  893.             if (isVerticalLine) {
  894.                 naturalBounds = new Rectangle2D.Float(
  895.                             -descent, leftOrTop, ascent + descent, lineDim);
  896.             }
  897.             else {
  898.                 naturalBounds = new Rectangle2D.Float(
  899.                             leftOrTop, -ascent, lineDim, ascent + descent);
  900.             }
  901.         }
  902.  
  903.         return naturalBounds;
  904.     }
  905.  
  906.     /**
  907.      * Create a copy of this layout.
  908.      */
  909.     protected Object clone() {
  910.         /*
  911.          * !!! I think this is safe.  Once created, nothing mutates the
  912.          * glyphsets or arrays.  But we need to make sure.
  913.          * {jbr} actually, that's not quite true.  The justification code
  914.          * mutates after cloning.  It doesn't actually change the glyphsets
  915.          * (that's impossible) but it replaces them with justified sets.  This
  916.          * is a problem for GlyphIterator creation, since new GlyphIterators
  917.          * are created by cloning a prototype.  If the prototype has outdated
  918.          * glyphsets, so will the new ones.  A partial solution is to set the
  919.          * prototypical GlyphIterator to null when the glyphsets change.  If
  920.          * you forget this one time, you're hosed.
  921.          */
  922.         try {
  923.             return super.clone();
  924.         }
  925.         catch (CloneNotSupportedException e) {
  926.             throw new InternalError();
  927.         }
  928.     }
  929.  
  930.     /*
  931.      * Utility to throw an expection if an invalid TextHitInfo is passed
  932.      * as a parameter.  Avoids code duplication.
  933.      */
  934.     private void checkTextHit(TextHitInfo hit) {
  935.         if (hit == null) {
  936.             throw new IllegalArgumentException("TextHitInfo is null.");
  937.         }
  938.  
  939.         if (hit.getInsertionIndex() < 0 ||
  940.             hit.getInsertionIndex() > characterCount) {
  941.             throw new IllegalArgumentException("TextHitInfo is out of range");
  942.         }
  943.     }
  944.  
  945.     /**
  946.      * Create a copy of this layout justified to the given width.
  947.      * <p>
  948.      * If this layout has already been justified, an exception is thrown.
  949.      * If this layout's justification ratio is zero, a layout identical to this
  950.      * one is returned.
  951.      *
  952.      * @param justificationWidth the width to use when justifying the line.
  953.      * For best results, it should not be too different from the current
  954.      * advance of the line.
  955.      * @return a layout justified to the given width.
  956.      * @exception Error if this layout has already been justified, an Error is
  957.      * thrown.
  958.      */
  959.     public TextLayout getJustifiedLayout(float justificationWidth) {
  960.  
  961.         if (justificationWidth <= 0) {
  962.                 throw new IllegalArgumentException("justificationWidth <= 0 passed to TextLayout.getJustifiedLayout()");
  963.         }
  964.  
  965.         if (justifyRatio == ALREADY_JUSTIFIED) {
  966.             throw new Error("Can't justify again.");
  967.         }
  968.  
  969.         TextLayout result = (TextLayout)clone();
  970.         if (justifyRatio > 0) {
  971.             result.handleJustify(justificationWidth);
  972.         }
  973.         result.justifyRatio = ALREADY_JUSTIFIED;
  974.  
  975.         return result;
  976.     }
  977.  
  978.     /**
  979.      * Justify this layout.  Overridden by subclassers to control justification.
  980.      *
  981.      * The layout will only justify if the paragraph attributes (from the
  982.      * source text, possibly defaulted by the layout attributes) indicate a
  983.      * non-zero justification ratio.  The text will be justified to the
  984.      * indicated width.  The current implementation also adjusts hanging
  985.      * punctuation and trailing whitespace to overhang the justification width.
  986.      * Once justified, the layout may not be rejustified.
  987.      * <p>
  988.      * Some code may rely on immutablity of layouts.  Subclassers should not
  989.      * call this directly, but instead should call getJustifiedLayout, which
  990.      * will call this method on a clone of this layout, preserving
  991.      * the original.
  992.      *
  993.      * @param justificationWidth the width to use when justifying the line.
  994.      * For best results, it should not be too different from the current
  995.      * advance of the line.
  996.      * @see #getJustifiedLayout
  997.      */
  998.     protected void handleJustify(float justificationWidth) {
  999.         if (justifyRatio > 0 && justificationWidth > 0) {
  1000.             newJustify(justificationWidth);
  1001.         }
  1002.     }
  1003.  
  1004.     // !!! this could be in the justifier object, if we decided to have one.
  1005.     private void newJustify(float justificationWidth) {
  1006.         /*
  1007.          * calculate visual limits of text needing justification
  1008.          * this excludes leading and trailing overhanging punctuation and
  1009.          * trailing whitespace leave limit at last char, we won't add
  1010.          * justification space after it, but do need to count its advance.
  1011.          */
  1012.  
  1013.         TextLayoutComponent[] newglyphs = new TextLayoutComponent[glyphs.length];
  1014.  
  1015.         float leftHang = 0;
  1016.         float adv = 0;
  1017.         float justifyDelta = 0;
  1018.         boolean rejustify = false;
  1019.         do {
  1020.             GlyphIterator iter = createGlyphIterator();
  1021.  
  1022.             adv = iter.totalAdvance();
  1023.  
  1024.             // System.out.println("advance: " + adv);
  1025.  
  1026.             float ignoredLeftAdvance = 0;
  1027.             float ignoredRightAdvance = 0;
  1028.  
  1029.             /*
  1030.              * ??? if we're a tabbed segment on a line, perhaps we don't hang
  1031.              * punctuation on one or both sides?
  1032.              * ??? Do we really want to hang punctuation?  I guess we do want
  1033.              * to hang whitespace, so we still need some of this code.
  1034.              */
  1035.             // {jbr} not hanging punctuation now
  1036.             boolean hangLeftPunctuation = false;
  1037.             boolean hangRightPunctuation = false;
  1038.  
  1039.             iter.firstVisualGlyph();
  1040.             if (!isDirectionLTR) {
  1041.                 while (iter.isValid() && iter.isWhitespace()) {
  1042.                     ignoredLeftAdvance += iter.distanceToNextGlyph();
  1043.                     iter.nextVisualGlyph();
  1044.                 }
  1045.             }
  1046.             if (hangLeftPunctuation) {
  1047.                 while (iter.isValid() && iter.isHangingPunctuation()) {
  1048.                     ignoredLeftAdvance += iter.distanceToNextGlyph();
  1049.                     iter.nextVisualGlyph();
  1050.                 }
  1051.             }
  1052.  
  1053.             int start;
  1054.  
  1055.             // Do we really want to adjust for bearings???
  1056.             // Remind: {jbr} I don't think so.  Bearings are not a design
  1057.             // metric;  they're a physical, bit-bounds metric.
  1058.             // Disabled for now.
  1059.             if (iter.isValid()) { // visual  for first character in segment
  1060.                 //ignoredLeftAdvance += iter.getLSB();
  1061.                 start = iter.visualIndex();
  1062.             }
  1063.             else {
  1064.                 start = characterCount;
  1065.             }
  1066.  
  1067.             leftHang = ignoredLeftAdvance;
  1068.  
  1069.             iter.lastVisualGlyph();
  1070.             if (isDirectionLTR) {
  1071.                 // System.out.print("s: " + start + ", l: " + iter.limit + ", (" + iter.visualIndex() + "," + iter.logicalIndex() + ")");
  1072.                 while (iter.isValid() && iter.visualIndex() > start &&
  1073.                        iter.isWhitespace()) {
  1074.                     ignoredRightAdvance += iter.distanceToNextGlyph();
  1075.                     iter.previousVisualGlyph();
  1076.                     // System.out.print(", (" + iter.visualIndex() + "," + iter.logicalIndex() + ")");
  1077.                 }
  1078.                 // System.out.println();
  1079.             }
  1080.             if (hangRightPunctuation) {
  1081.                 while (iter.isValid() && iter.visualIndex() > start &&
  1082.                        iter.isHangingPunctuation()) {
  1083.                     ignoredRightAdvance += iter.distanceToNextGlyph();
  1084.                     iter.previousVisualGlyph();
  1085.                 }
  1086.             }
  1087.             // Do we really want to adjust for bearings???
  1088.             // Remind {jbr} No - see above.
  1089.             if (iter.isValid()) {// visual adjust for last character in segment
  1090.                 //ignoredRightAdvance += iter.getRSB();
  1091.             }
  1092.             int end = iter.visualIndex();
  1093.  
  1094.             /*
  1095.              * get the advance of the text that has to fit the justification
  1096.              * width
  1097.              */
  1098.             float justifyAdvance =
  1099.                     adv - ignoredLeftAdvance - ignoredRightAdvance;
  1100.  
  1101.             // get the actual justification delta
  1102.             justifyDelta = (justificationWidth - justifyAdvance) * justifyRatio;
  1103.  
  1104.             /*
  1105.              * generate an array of GlyphJustificationInfo records to pass to
  1106.              * the justifier
  1107.              */
  1108.             int glyphCount = 0;
  1109.             for (int i = 0; i < glyphs.length; i++) {
  1110.                 glyphCount += glyphs[i].getNumGlyphs();
  1111.             }
  1112.             GlyphJustificationInfo[] info =
  1113.                                 new GlyphJustificationInfo[glyphCount];
  1114.  
  1115.             if (start < characterCount) {
  1116.                 iter.setVisualGlyph(start);
  1117.                 while (iter.isValid() && iter.visualIndex() <= end) {
  1118.  
  1119.                     boolean glyphIsLTR = iter.glyphIsLTR();
  1120.                     GlyphJustificationInfo[] gi = iter.glyphJustificationInfos();
  1121.  
  1122.                     for (int i=0; i < gi.length; i++) {
  1123.                         int off = glyphIsLTR?  i  :  gi.length - i - 1;
  1124.                         info[iter.visualIndex() + off] = gi[i];
  1125.                     }
  1126.  
  1127.                     iter.nextVisualGlyph();
  1128.                 }
  1129.             }
  1130.  
  1131.             // invoke justifier on the records
  1132.             // ignore left of start and right of end
  1133.             TextJustifier justifier = new TextJustifier(info, start, end + 1);
  1134.  
  1135.             float[] deltas = justifier.justify(justifyDelta);
  1136.  
  1137.             /*
  1138.              * ??? How do you position hanging punctuation when you've
  1139.              * justified a line?  Should you not hang it when the line has to
  1140.              * stretch too much?  If you compress the line, should you also
  1141.              * compress the punctuation to match the compression that would
  1142.              * have been applied had it not been hanging?
  1143.              * go through glyphsets in visual order, applying justification
  1144.              * flags is a hack reference param for java on entry, true if can
  1145.              * substitute glyphset that requires rejustification on return,
  1146.              * true if rejustification is required
  1147.              */
  1148.  
  1149.             boolean canRejustify = rejustify == false;
  1150.             boolean wantRejustify = false;
  1151.             boolean[] flags = new boolean[1];
  1152.  
  1153.             int index = 0;
  1154.             for (int vi = 0; vi < glyphs.length; vi++) {
  1155.                 int i = (glyphsOrder == null) ? vi : glyphsOrder[vi];
  1156.                 int numGlyphs = glyphs[i].getNumGlyphs();
  1157.                 flags[0] = canRejustify;
  1158.                 //System.out.println(vi + "/" + i + " dl: " + deltas.length + ", i: " + index + ", gn: " + numGlyphs);
  1159.                 newglyphs[i] = glyphs[i].applyJustification(deltas, index, flags);
  1160.                 if (flags[0]) {
  1161.                     glyphs[i] = newglyphs[i]; // need to process new codes
  1162.                 }
  1163.  
  1164.                 wantRejustify |= flags[0];
  1165.  
  1166.                 index += numGlyphs * 2;
  1167.             }
  1168.  
  1169.             rejustify = wantRejustify && !rejustify; // only make two passes
  1170.             protoIterator = null; // force iterator to reflect new glyphsets
  1171.         } while (rejustify);
  1172.  
  1173.         // {jbr} No hanging characters anymore.
  1174.         //if (isVerticalLine) {
  1175.         //    dy = leftHang;
  1176.         //} else {
  1177.         //    dx = leftHang;
  1178.         //}
  1179.         glyphs = newglyphs;
  1180.         advance = adv + justifyDelta;
  1181.     }
  1182.  
  1183.     /**
  1184.      * Return the baseline for this layout.
  1185.      *
  1186.      * The baseline is one of the values defined in Font (roman, centered,
  1187.      * hanging).  Ascent and descent are relative to this baseline.  The
  1188.      * baselineOffsets are also relative to this baseline.
  1189.      *
  1190.      * @see #getBaselineOffsets
  1191.      * @see Font
  1192.      */
  1193.     public byte getBaseline() {
  1194.         return baseline;
  1195.     }
  1196.  
  1197.     /**
  1198.      * Return the offsets array for the baselines used for this layout.
  1199.      * <p>
  1200.      * The array is indexed by one of the values defined in Font (roman,
  1201.      * centered, hanging).  The values are relative to this layout's baseline,
  1202.      * so that getBaselineOffsets[getBaseline()] == 0.  Offsets
  1203.      * are added to the position of the layout's baseline to get the position
  1204.      * for the new baseline.
  1205.      *
  1206.      * @see #getBaseline
  1207.      * @see Font
  1208.      */
  1209.     public float[] getBaselineOffsets() {
  1210.         float[] offsets = new float[baselineOffsets.length];
  1211.         System.arraycopy(baselineOffsets, 0, offsets, 0, offsets.length);
  1212.         return offsets;
  1213.     }
  1214.  
  1215.     /**
  1216.      * Return the advance of the layout.
  1217.      *
  1218.      * The advance is the distance from the origin to the advance of the
  1219.      * rightmost (bottommost) character measuring in the line direction.
  1220.      */
  1221.     public float getAdvance() {
  1222.         ensureCache();
  1223.         return advance;
  1224.     }
  1225.  
  1226.     /**
  1227.      * Return the advance of the layout, minus trailing whitespace.
  1228.      *
  1229.      * @see #getAdvance
  1230.      */
  1231.     public float getVisibleAdvance() {
  1232.         ensureCache();
  1233.         return visibleAdvance;
  1234.     }
  1235.  
  1236.     /**
  1237.      * Return the ascent of the layout.
  1238.      *
  1239.      * The ascent is the distance from the top (right) of the layout to the
  1240.      * baseline.  It is always positive or zero.  The ascent is sufficient to
  1241.      * accomodate superscripted text and is the maximum of the sum of the the
  1242.      * ascent, offset, and baseline of each glyph.
  1243.      */
  1244.     public float getAscent() {
  1245.         ensureCache();
  1246.         return ascent;
  1247.     }
  1248.  
  1249.     /**
  1250.      * Return the descent of the layout.
  1251.      *
  1252.      * The descent is the distance from the baseline to the bottom (left) of
  1253.      * the layout.  It is always positive or zero.  The descent is sufficient
  1254.      * to accomodate subscripted text and is maximum of the sum of the descent,
  1255.      * offset, and baseline of each glyph.
  1256.      */
  1257.     public float getDescent() {
  1258.         ensureCache();
  1259.         return descent;
  1260.     }
  1261.  
  1262.     /**
  1263.      * Return the leading of the layout.
  1264.      *
  1265.      * The leading is the suggested interline spacing for this layout.
  1266.      * <p>
  1267.      * The leading is computed from the leading, descent, and baseline
  1268.      * of all glyphsets in the layout.  The algorithm is roughly as follows:
  1269.      * <blockquote><pre>
  1270.      * maxD = 0;
  1271.      * maxDL = 0;
  1272.      * for (GlyphSet g in all glyphsets) {
  1273.      *    maxD = max(maxD, g.getDescent() + offsets[g.getBaseline()]);
  1274.      *    maxDL = max(maxDL, g.getDescent() + g.getLeading() +
  1275.      *                       offsets[g.getBaseline()]);
  1276.      * }
  1277.      * return maxDL - maxD;
  1278.      * </pre></blockquote>
  1279.      */
  1280.     public float getLeading() {
  1281.         ensureCache();
  1282.         return leading;
  1283.     }
  1284.  
  1285.     /**
  1286.      * Return the bounds of the layout.
  1287.      *
  1288.      * This contains all of the pixels the layout can draw.  It may not
  1289.      * coincide exactly with the ascent, descent, origin or advance of
  1290.      * the layout.
  1291.      */
  1292.     public Rectangle2D getBounds() {
  1293.         ensureCache();
  1294.  
  1295.         if (boundsRect == null) {
  1296.  
  1297.             float gsAdvance = isVerticalLine? -dy : -dx;
  1298.             float left = Float.MAX_VALUE, right = Float.MIN_VALUE;
  1299.             float top = Float.MAX_VALUE, bottom = Float.MIN_VALUE;
  1300.  
  1301.             for (int i=0; i < glyphs.length; i++) {
  1302.  
  1303.                 TextLayoutComponent gs;
  1304.                 gs = glyphsOrder==null? glyphs[i] : glyphs[glyphsOrder[i]];
  1305.                 Rectangle2D gsBounds = gs.getBounds(this);
  1306.  
  1307.                 left = Math.min(left, (float) gsBounds.getLeft() + gsAdvance);
  1308.                 right = Math.max(right, (float) gsBounds.getRight() + gsAdvance);
  1309.                 gsAdvance += gs.getAdvance();
  1310.  
  1311.                 float shift = baselineOffsets[gs.getBaseline()];
  1312.  
  1313.                 top = Math.min(top, (float) gsBounds.getTop()+shift);
  1314.                 bottom = Math.max(bottom, (float) gsBounds.getBottom()+shift);
  1315.             }
  1316.  
  1317.             boundsRect = new Rectangle2D.Float(left, top, right-left, bottom-top);
  1318.         }
  1319.  
  1320.         Rectangle2D bounds = new Rectangle2D.Float();
  1321.         bounds.setRect(boundsRect);
  1322.  
  1323.         // remind!!  hack!!
  1324.         boundsRect = null;
  1325.         return bounds;
  1326.     }
  1327.  
  1328.     /**
  1329.      * Return true if the layout is left-to-right.
  1330.      *
  1331.      * The layout has a base direction of either left-to-right (LTR) or
  1332.      * right-to-left (RTL).  This is independent of the actual direction of
  1333.      * text on the line, which may be either or mixed. Left-to-right layouts
  1334.      * by default should position flush left, and if on a tabbed line, the
  1335.      * tabs run left to right, so that logically successive layouts position
  1336.      * left to right.  The opposite is true for RTL layouts. By default they
  1337.      * should position flush left, and tabs run right-to-left.
  1338.      *
  1339.      * On vertical lines all text runs top-to-bottom, and is treated as LTR.
  1340.      */
  1341.     public boolean isLeftToRight() {
  1342.         return isDirectionLTR;
  1343.     }
  1344.  
  1345.     /**
  1346.      * Return true if the layout is vertical.
  1347.      */
  1348.     public boolean isVertical() {
  1349.         return isVerticalLine;
  1350.     }
  1351.  
  1352.     /**
  1353.      * Return the number of characters represented by this layout.
  1354.      */
  1355.     public int getCharacterCount() {
  1356.         ensureCache();
  1357.         return characterCount;
  1358.     }
  1359.  
  1360.     /*
  1361.      * carets and hit testing
  1362.      *
  1363.      * Positions on a text line are represented by instances of TextHitInfo.
  1364.      * Any TextHitInfo with characterOffset between 0 and characterCount-1,
  1365.      * inclusive, represents a valid position on the line.  Additionally,
  1366.      * [-1, trailing] and [characterCount, leading] are valid positions, and
  1367.      * represent positions at the logical start and end of the line,
  1368.      * respectively.
  1369.      *
  1370.      * The characterOffsets in TextHitInfo's used and returned by TextLayout
  1371.      * are relative to the beginning of the text layout, not necessarily to
  1372.      * the beginning of the text storage the client is using.
  1373.      *
  1374.      *
  1375.      * Every valid TextHitInfo has either one or two carets associated with it.
  1376.      * A caret is a visual location in the TextLayout indicating where text at
  1377.      * the TextHitInfo will be displayed on screen.  If a TextHitInfo
  1378.      * represents a location on a directional boundary, then there are two
  1379.      * possible visible positions for newly inserted text.  Consider the
  1380.      * following example, in which capital letters indicate right-to-left text,
  1381.      * and the overall line direction is left-to-right:
  1382.      *
  1383.      * Text Storage: [ a, b, C, D, E, f ]
  1384.      * Display:        a b E D C f
  1385.      *
  1386.      * The text hit info (1, t) represents the trailing side of 'b'.  If 'q',
  1387.      * a left-to-right character is inserted into the text storage at this
  1388.      * location, it will be displayed between the 'b' and the 'E':
  1389.      *
  1390.      * Text Storage: [ a, b, q, C, D, E, f ]
  1391.      * Display:        a b q E D C f
  1392.      *
  1393.      * However, if a 'W', which is right-to-left, is inserted into the storage
  1394.      * after 'b', the storage and display will be:
  1395.      *
  1396.      * Text Storage: [ a, b, W, C, D, E, f ]
  1397.      * Display:        a b E D C W f
  1398.      *
  1399.      * So, for the original text storage, two carets should be displayed for
  1400.      * location (1, t): one visually between 'b' and 'E' and one visually
  1401.      * between 'C' and 'f'.
  1402.      *
  1403.      *
  1404.      * When two carets are displayed for a TextHitInfo, one caret is the
  1405.      * 'strong' caret and the other is the 'weak' caret.  The strong caret
  1406.      * indicates where an inserted character will be displayed when that
  1407.      * character's direction is the same as the direction of the TextLayout.
  1408.      * The weak caret shows where an character inserted character will be
  1409.      * displayed when the character's direction is opposite that of the
  1410.      * TextLayout.
  1411.      *
  1412.      *
  1413.      * Clients should not be overly concerned with the details of correct
  1414.      * caret display. TextLayout.getCarets(TextHitInfo) will return an
  1415.      * array of two paths representing where carets should be displayed.
  1416.      * The first path in the array is the strong caret; the second element,
  1417.      * if non-null, is the weak caret.  If the second element is null,
  1418.      * then there is no weak caret for the given TextHitInfo.
  1419.      *
  1420.      *
  1421.      * Since text can be visually reordered, logically consecutive
  1422.      * TextHitInfo's may not be visually consecutive.  One implication of this
  1423.      * is that a client cannot tell from inspecting a TextHitInfo whether the
  1424.      * hit represents the first (or last) caret in the layout.  Clients
  1425.      * can call getVisualOtherHit();  if the visual companion is
  1426.      * (-1, TRAILING) or (characterCount, LEADING), then the hit is at the
  1427.      * first (last) caret position in the layout.
  1428.      */
  1429.  
  1430.     private float[] getCaretInfo(int caret, Rectangle2D bounds) {
  1431.  
  1432.         float top1X, top2X;
  1433.         float bottom1X, bottom2X;
  1434.  
  1435.         GlyphIterator iter = createGlyphIterator();
  1436.         if (caret == 0 || caret == characterCount) {
  1437.  
  1438.             float pos;
  1439.             if (caret == characterCount) {
  1440.                 iter.setVisualGlyph(characterCount-1);
  1441.                 pos = iter.glyphLinePosition() + iter.glyphAdvance();
  1442.             }
  1443.             else {
  1444.                 pos = iter.glyphLinePosition();
  1445.             }
  1446.             float angle = iter.glyphAngle();
  1447.             top1X = top2X = pos + angle*iter.glyphAscent();
  1448.             bottom1X = bottom2X = pos - angle*iter.glyphDescent();
  1449.         }
  1450.         else {
  1451.  
  1452.             {
  1453.                 iter.setVisualGlyph(caret-1);
  1454.                 float angle1 = iter.glyphAngle();
  1455.                 float pos1 = iter.glyphLinePosition() + iter.glyphAdvance();
  1456.                 top1X = pos1 + angle1*iter.glyphAscent();
  1457.                 bottom1X = pos1 - angle1*iter.glyphDescent();
  1458.             }
  1459.             {
  1460.                 iter.nextVisualGlyph();
  1461.                 float angle2 = iter.glyphAngle();
  1462.                 float pos2 = iter.glyphLinePosition();
  1463.                 top2X = pos2 + angle2*iter.glyphAscent();
  1464.                 bottom2X = pos2 - angle2*iter.glyphDescent();
  1465.             }
  1466.         }
  1467.  
  1468.         float topX = (top1X + top2X) / 2;
  1469.         float bottomX = (bottom1X + bottom2X) / 2;
  1470.  
  1471.         float[] info = new float[2];
  1472.  
  1473.         if (isVerticalLine) {
  1474.             info[1] = (float) ((topX - bottomX) / bounds.getWidth());
  1475.             info[0] = (float) (topX + (info[1]*bounds.getLeft()));
  1476.         }
  1477.         else {
  1478.             info[1] = (float) ((topX - bottomX) / bounds.getHeight());
  1479.             info[0] = (float) (bottomX + (info[1]*bounds.getBottom()));
  1480.         }
  1481.  
  1482.         return info;
  1483.     }
  1484.  
  1485.  
  1486.     /**
  1487.      * Return an array of two floats describing the caret.
  1488.      *
  1489.      * result[0] is offset along advance on the baseline,
  1490.      * result[1] is run/rise
  1491.      *
  1492.      * The caret at the start of the line has the position and angle of the
  1493.      * first glyph at the base. The caret at the end of the line has the
  1494.      * start + advance and angle of the last glyph at the base.
  1495.      * Intermediate carets average the values for the left and right glyphs.
  1496.      *
  1497.      * Clients who wish to compute their own carets can access the base
  1498.      * information used by the default caret code.  The caret position is
  1499.      * also required to support arrow-key navigation from line to line.
  1500.      *
  1501.      * @param caret the caret index
  1502.      */
  1503.     private float[] oldGetCaretInfo(int caret, Rectangle2D bounds) {
  1504.         float x;
  1505.         float y;
  1506.         float a;
  1507.  
  1508.         GlyphIterator iter = createGlyphIterator();
  1509.         if (caret == 0) {
  1510.             iter.setVisualGlyph(0);
  1511.             x = iter.glyphXPosition();
  1512.             y = iter.glyphYPosition();
  1513.             a = iter.glyphAngle();
  1514.         } else if (caret == characterCount) {
  1515.             iter.setVisualGlyph(characterCount - 1);
  1516.             x = iter.glyphXPosition();
  1517.             y = iter.glyphYPosition();
  1518.             a = iter.glyphAngle();
  1519.             float adv = iter.glyphAdvance();
  1520.             if (isVerticalLine) {
  1521.                 y += adv;
  1522.             } else {
  1523.                 x += adv;
  1524.             }
  1525.         } else {
  1526.             iter.setVisualGlyph(caret);
  1527.             float xr = iter.glyphXPosition();
  1528.             float yr = iter.glyphYPosition();
  1529.             float ar = iter.glyphAngle();
  1530.  
  1531.             iter.previousVisualGlyph();
  1532.             float xl = iter.glyphXPosition();
  1533.             float yl = iter.glyphYPosition();
  1534.             float al = iter.glyphAngle();
  1535.             float adv = iter.glyphAdvance();
  1536.             if (isVerticalLine) {
  1537.                 yl += adv;
  1538.             } else {
  1539.                 xl += adv;
  1540.             }
  1541.  
  1542.             x = (xr + xl) / 2;
  1543.             y = (yr + yl) / 2;
  1544.             a = (ar + al) / 2; // not really the half angle, but it will do
  1545.         }
  1546.  
  1547.         // System.out.println("caret: " + caret + ", x: " + x + ", y: " + y + ", a: " + a);
  1548.  
  1549.         float[] result = new float[2];
  1550.         if (isVerticalLine) {
  1551.             result[0] = y + x*a;
  1552.         } else {
  1553.             result[0] = x + y*a;
  1554.         }
  1555.         result[1] = a;
  1556.  
  1557.         return result;
  1558.     }
  1559.  
  1560.     /**
  1561.      * Return information about the caret corresponding to hit.
  1562.      *
  1563.      * The first element of the array is the intersection of the caret with
  1564.      * the baseline. The second element of the array is the slope (run/rise)
  1565.      * of the caret.
  1566.      * <p>
  1567.      * This method is meant for informational use.  To display carets, it
  1568.      * is better to use <code>getCarets</code>.
  1569.      *
  1570.      * @param hit a hit on a character in this layout
  1571.      * @param bounds the bounds to which the caret info is constructed
  1572.      * @return a two-element array containing the position and slope of
  1573.      * the caret
  1574.      *
  1575.      * @see #getCarets
  1576.      */
  1577.     public float[] getCaretInfo(TextHitInfo hit, Rectangle2D bounds) {
  1578.         ensureCache();
  1579.         checkTextHit(hit);
  1580.  
  1581.         return getCaretInfo(hitToCaret(hit), bounds);
  1582.     }
  1583.  
  1584.     /**
  1585.      * A convenience overload using the natural bounds of this layout.
  1586.      */
  1587.     public float[] getCaretInfo(TextHitInfo hit) {
  1588.  
  1589.         return getCaretInfo(hit, getNaturalBounds());
  1590.     }
  1591.  
  1592.     /**
  1593.      * Return a caret index corresponding to the hit.
  1594.      *
  1595.      * Carets are numbered from left to right (top to bottom) starting from
  1596.      * zero. This always places carets next to the character hit, on the
  1597.      * indicated side of the character.
  1598.      */
  1599.     private int hitToCaret(TextHitInfo hit) {
  1600.  
  1601.         if (hit.getCharIndex() < 0) {
  1602.             return isDirectionLTR ? 0 : characterCount;
  1603.         } else if (hit.getCharIndex() >= characterCount) {
  1604.             return isDirectionLTR ? characterCount : 0;
  1605.         }
  1606.  
  1607.         GlyphIterator iter = createGlyphIterator();
  1608.  
  1609.         // {jbr} changed slightly.  Doesn't try to skip ligatures / combining chars.
  1610.  
  1611.         iter.setLogicalGlyph(hit.getCharIndex());
  1612.  
  1613.         if (hit.isLeadingEdge() != iter.glyphIsLTR()) {
  1614.             // ie if hit is on right side of glyph
  1615.             iter.nextVisualGlyph();
  1616.         }
  1617.  
  1618.         int caret;
  1619.  
  1620.         if (iter.isValid()) {
  1621.             caret = iter.visualIndex();
  1622.         } else {
  1623.             caret = characterCount;
  1624.         }
  1625.  
  1626.         return caret;
  1627.     }
  1628.  
  1629.     /**
  1630.      * Given a caret index, return a hit whose caret is at the index.
  1631.      * The hit is NOT guaranteed to be strong!!!
  1632.      *
  1633.      * @param caret a caret index.
  1634.      * @return a hit on this layout whose strong caret is at the requested
  1635.      * index.
  1636.      */
  1637.     private TextHitInfo caretToHit(int caret) {
  1638.  
  1639.         if (caret == 0 || caret == characterCount) {
  1640.  
  1641.             if ((caret == characterCount) == isDirectionLTR) {
  1642.                 return TextHitInfo.leading(characterCount);
  1643.             }
  1644.             else {
  1645.                 return TextHitInfo.trailing(-1);
  1646.             }
  1647.         }
  1648.         else {
  1649.             GlyphIterator iter = createGlyphIterator();
  1650.  
  1651.             iter.setVisualGlyph(caret);
  1652.  
  1653.             int charIndex = iter.logicalIndex();
  1654.             boolean leading = iter.glyphIsLTR();
  1655.  
  1656.             return leading? TextHitInfo.leading(charIndex)
  1657.                             : TextHitInfo.trailing(charIndex);
  1658.         }
  1659.     }
  1660.  
  1661.     private boolean iterIsAtValidCaret(GlyphIterator iter) {
  1662.  
  1663.         if (!iter.isValid() || iter.isStandard() || iter.isLigature() || iter.logicalIndex()==0) {
  1664.             return true;
  1665.         }
  1666.  
  1667.         if (iter.isCombining()) {
  1668.             return false;
  1669.         }
  1670.  
  1671.         if (iter.isFiller()) {
  1672.             return caretsInLigaturesAreAllowed;
  1673.         } else {
  1674.             throw new Error("Unanticipated iterator state in iterIsAtValidCaret().");
  1675.         }
  1676.     }
  1677.  
  1678.     /**
  1679.      * Return the hit for the next caret to the right (bottom); if no
  1680.      * such hit, return null.
  1681.      *
  1682.      * If the hit character index is out of bounds, an IllegalArgumentException
  1683.      * is thrown.
  1684.      *
  1685.      * @param hit a hit on a character in this layout.
  1686.      * @return a hit whose caret appears at the next position to the
  1687.      * right (bottom) of the caret of the provided hit, or null.
  1688.      */
  1689.     public TextHitInfo getNextRightHit(TextHitInfo hit) {
  1690.         ensureCache();
  1691.         checkTextHit(hit);
  1692.  
  1693.         int caret = hitToCaret(hit);
  1694.  
  1695.         if (caret == getCharacterCount()) {
  1696.             return null;
  1697.         }
  1698.  
  1699.         GlyphIterator iter = createGlyphIterator();
  1700.  
  1701.         iter.setVisualGlyph(caret);
  1702.         do {
  1703.             iter.nextVisualGlyph();
  1704.         } while (!iterIsAtValidCaret(iter));
  1705.  
  1706.         caret = iter.isValid()? iter.visualIndex() : characterCount;
  1707.  
  1708.         hit = caretToHit(caret);
  1709.         return hit;
  1710.     }
  1711.  
  1712.     /**
  1713.      * Return the hit for the next caret to the right (bottom); if no
  1714.      * such hit, return null.  The hit is to the right of the strong
  1715.      * caret at the given offset, as determined by the given caret
  1716.      * policy.
  1717.      * The returned hit is the stronger of the two possible
  1718.      * hits, as determined by the given caret policy.
  1719.      *
  1720.      * @param offset An insertion offset in this layout.  Cannot be
  1721.      * less than 0 or greater than the layout's character count.
  1722.      * @param policy The policy used to select the strong caret.
  1723.      * @return a hit whose caret appears at the next position to the
  1724.      * right (bottom) of the caret of the provided hit, or null.
  1725.      */
  1726.     public TextHitInfo getNextRightHit(int offset, CaretPolicy policy) {
  1727.  
  1728.         if (offset < 0 || offset > characterCount) {
  1729.             throw new IllegalArgumentException("Offset out of bounds in TextLayout.getNextRightHit()");
  1730.         }
  1731.  
  1732.         if (policy == null) {
  1733.             throw new IllegalArgumentException("Null CaretPolicy passed to TextLayout.getCarets()");
  1734.         }
  1735.  
  1736.         TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
  1737.         TextHitInfo hit2 = hit1.getOtherHit();
  1738.  
  1739.         TextHitInfo nextHit = getNextRightHit(policy.getStrongCaret(hit1, hit2, this));
  1740.         TextHitInfo otherHit = getVisualOtherHit(nextHit);
  1741.  
  1742.         return policy.getStrongCaret(otherHit, nextHit, this);
  1743.     }
  1744.  
  1745.     /**
  1746.      * Return the hit for the next caret to the right (bottom); if no
  1747.      * such hit, return null.  The hit is to the right of the strong
  1748.      * caret at the given offset, as determined by the default caret
  1749.      * policy.
  1750.      * The returned hit is the stronger of the two possible
  1751.      * hits, as determined by the default caret policy.
  1752.      *
  1753.      * @param offset An insertion offset in this layout.  Cannot be
  1754.      * less than 0 or greater than the layout's character count.
  1755.      * @return a hit whose caret appears at the next position to the
  1756.      * right (bottom) of the caret of the provided hit, or null.
  1757.      */
  1758.     public TextHitInfo getNextRightHit(int offset) {
  1759.  
  1760.         return getNextRightHit(offset, DEFAULT_CARET_POLICY);
  1761.     }
  1762.  
  1763.     /**
  1764.      * Return the hit for the next caret to the left (top); if no such
  1765.      * hit, return null.
  1766.      *
  1767.      * If the hit character index is out of bounds, an IllegalArgumentException
  1768.      * is thrown.
  1769.      *
  1770.      * @param hit a hit on a character in this layout.
  1771.      * @return a hit whose caret appears at the next position to the
  1772.      * left (top) of the caret of the provided hit, or null.
  1773.      */
  1774.     public TextHitInfo getNextLeftHit(TextHitInfo hit) {
  1775.         ensureCache();
  1776.         checkTextHit(hit);
  1777.  
  1778.         int caret = hitToCaret(hit);
  1779.  
  1780.         if (caret == 0) {
  1781.             return null;
  1782.         }
  1783.  
  1784.         GlyphIterator iter = createGlyphIterator();
  1785.  
  1786.         iter.setVisualGlyph(caret-1);
  1787.  
  1788.         while (!iterIsAtValidCaret(iter)) {
  1789.             iter.previousVisualGlyph();
  1790.         }
  1791.  
  1792.         caret = iter.isValid()? iter.visualIndex() : 0;
  1793.  
  1794.         hit = caretToHit(caret);
  1795.         return hit;
  1796.     }
  1797.  
  1798.     /**
  1799.      * Return the hit for the next caret to the left (top); if no
  1800.      * such hit, return null.  The hit is to the left of the strong
  1801.      * caret at the given offset, as determined by the given caret
  1802.      * policy.
  1803.      * The returned hit is the stronger of the two possible
  1804.      * hits, as determined by the given caret policy.
  1805.      *
  1806.      * @param offset An insertion offset in this layout.  Cannot be
  1807.      * less than 0 or greater than the layout's character count.
  1808.      * @param policy The policy used to select the strong caret.
  1809.      * @return a hit whose caret appears at the next position to the
  1810.      * left (top) of the caret of the provided hit, or null.
  1811.      */
  1812.     public TextHitInfo getNextLeftHit(int offset, CaretPolicy policy) {
  1813.  
  1814.         if (policy == null) {
  1815.             throw new IllegalArgumentException("Null CaretPolicy passed to TextLayout.getNextLeftHit()");
  1816.         }
  1817.  
  1818.         if (offset < 0 || offset > characterCount) {
  1819.             throw new IllegalArgumentException("Offset out of bounds in TextLayout.getNextLeftHit()");
  1820.         }
  1821.  
  1822.         TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
  1823.         TextHitInfo hit2 = hit1.getOtherHit();
  1824.  
  1825.         TextHitInfo nextHit = getNextLeftHit(policy.getStrongCaret(hit1, hit2, this));
  1826.         TextHitInfo otherHit = getVisualOtherHit(nextHit);
  1827.  
  1828.         return policy.getStrongCaret(otherHit, nextHit, this);
  1829.     }
  1830.  
  1831.     /**
  1832.      * Return the hit for the next caret to the left (top); if no
  1833.      * such hit, return null.  The hit is to the left of the strong
  1834.      * caret at the given offset, as determined by the default caret
  1835.      * policy.
  1836.      * The returned hit is the stronger of the two possible
  1837.      * hits, as determined by the default caret policy.
  1838.      *
  1839.      * @param offset An insertion offset in this layout.  Cannot be
  1840.      * less than 0 or greater than the layout's character count.
  1841.      * @return a hit whose caret appears at the next position to the
  1842.      * left (top) of the caret of the provided hit, or null.
  1843.      */
  1844.     public TextHitInfo getNextLeftHit(int offset) {
  1845.  
  1846.         return getNextLeftHit(offset, DEFAULT_CARET_POLICY);
  1847.     }
  1848.  
  1849.     /**
  1850.      * Return the hit on the opposite side of this hit's caret.
  1851.      */
  1852.     public TextHitInfo getVisualOtherHit(TextHitInfo hit) {
  1853.  
  1854.         ensureCache();
  1855.         checkTextHit(hit);
  1856.  
  1857.         GlyphIterator iter = createGlyphIterator();
  1858.         int hitCharIndex = hit.getCharIndex();
  1859.  
  1860.         int charIndex;
  1861.         boolean leading;
  1862.  
  1863.         if (hitCharIndex == -1 || hitCharIndex == characterCount) {
  1864.  
  1865.             if (isDirectionLTR == (hitCharIndex == -1)) {
  1866.                 iter.firstVisualGlyph();
  1867.             }
  1868.             else {
  1869.                 iter.lastVisualGlyph();
  1870.             }
  1871.  
  1872.             charIndex = iter.logicalIndex();
  1873.  
  1874.             if (isDirectionLTR == (hitCharIndex == -1)) {
  1875.                 // at left end
  1876.                 leading = iter.glyphIsLTR();
  1877.             }
  1878.             else {
  1879.                 // at right end
  1880.                 leading = !iter.glyphIsLTR();
  1881.             }
  1882.         }
  1883.         else {
  1884.  
  1885.             iter.setLogicalGlyph(hitCharIndex);
  1886.             boolean movedToRight;
  1887.             if (iter.glyphIsLTR() == hit.isLeadingEdge()) {
  1888.                 iter.previousVisualGlyph();
  1889.                 movedToRight = false;
  1890.             }
  1891.             else {
  1892.                 iter.nextVisualGlyph();
  1893.                 movedToRight = true;
  1894.             }
  1895.  
  1896.             if (iter.isValid()) {
  1897.                 charIndex = iter.logicalIndex();
  1898.                 leading = movedToRight == iter.glyphIsLTR();
  1899.             }
  1900.             else {
  1901.                 charIndex =
  1902.                     (movedToRight == isDirectionLTR)? characterCount : -1;
  1903.                 leading = charIndex == characterCount;
  1904.             }
  1905.         }
  1906.  
  1907.         return leading? TextHitInfo.leading(charIndex) :
  1908.                                 TextHitInfo.trailing(charIndex);
  1909.     }
  1910.  
  1911.     /**
  1912.      * Return an array of four floats corresponding the endpoints of the caret
  1913.      * x0, y0, x1, y1.
  1914.      *
  1915.      * This creates a line along the slope of the caret intersecting the
  1916.      * baseline at the caret
  1917.      * position, and extending from ascent above the baseline to descent below
  1918.      * it.
  1919.      */
  1920.     private double[] getCaretPath(int caret, Rectangle2D bounds,
  1921.                                   boolean clipToBounds) {
  1922.  
  1923.         float[] info = getCaretInfo(caret, bounds);
  1924.  
  1925.         double pos = info[0];
  1926.         double slope = info[1];
  1927.  
  1928.         double x0, y0, x1, y1;
  1929.         double x2 = -3141.59, y2 = -2.7; // values are there to make compiler happy
  1930.  
  1931.         double left = bounds.getX();
  1932.         double right = left + bounds.getWidth();
  1933.         double top = bounds.getY();
  1934.         double bottom = top + bounds.getHeight();
  1935.  
  1936.         boolean threePoints = false;
  1937.  
  1938.         if (isVerticalLine) {
  1939.  
  1940.             if (slope >= 0) {
  1941.                 x0 = left;
  1942.                 x1 = right;
  1943.                 y0 = pos + x0 * slope;
  1944.                 y1 = pos + x1 * slope;
  1945.             }
  1946.             else {
  1947.                 x1 = left;
  1948.                 x0 = right;
  1949.                 y1 = pos + x1 * slope;
  1950.                 y0 = pos + x0 * slope;
  1951.             }
  1952.  
  1953.             // y0 <= y1, always
  1954.             if (clipToBounds) {
  1955.                 if (y0 < top) {
  1956.                     if (slope == 0 || y1 <= top) {
  1957.                         y0 = y1 = top;
  1958.                     }
  1959.                     else {
  1960.                         threePoints = true;
  1961.                         y2 = top;
  1962.                         x2 = x0 + (top-y0)/slope;
  1963.                         y0 = top;
  1964.                         if (y1 > bottom)
  1965.                             y1 = bottom;
  1966.                     }
  1967.                 }
  1968.                 else if (y1 > bottom) {
  1969.                     if (slope == 0 || y0 >= bottom) {
  1970.                         y0 = y1 = bottom;
  1971.                     }
  1972.                     else {
  1973.                         threePoints = true;
  1974.                         y2 = bottom;
  1975.                         x2 = x1 - (y1-bottom)/slope;
  1976.                         y1 = bottom;
  1977.                     }
  1978.                 }
  1979.             }
  1980.  
  1981.         }
  1982.         else {
  1983.  
  1984.             if (slope >= 0) {
  1985.                 y0 = bottom;
  1986.                 y1 = top;
  1987.                 x0 = pos - y0 * slope;
  1988.                 x1 = pos - y1 * slope;
  1989.             }
  1990.             else {
  1991.                 y1 = bottom;
  1992.                 y0 = top;
  1993.                 x1 = pos - y0 * slope;
  1994.                 x0 = pos - y1 * slope;
  1995.             }
  1996.             // x0 <= x1, always
  1997.  
  1998.             if (clipToBounds) {
  1999.                 if (x0 < left) {
  2000.                     if (slope == 0 || x1 <= left) {
  2001.                         x0 = x1 = left;
  2002.                     }
  2003.                     else {
  2004.                         threePoints = true;
  2005.                         x2 = left;
  2006.                         y2 = y0 - (left-x0)/slope;
  2007.                         x0 = left;
  2008.                         if (x1 > right)
  2009.                             x1 = right;
  2010.                     }
  2011.                 }
  2012.                 else if (x1 > right) {
  2013.                     if (slope == 0 || x0 >= right) {
  2014.                         x0 = x1 = right;
  2015.                     }
  2016.                     else {
  2017.                         threePoints = true;
  2018.                         x2 = right;
  2019.                         y2 = y1 + (x1-right)/slope;
  2020.                         x1 = right;
  2021.                     }
  2022.                 }
  2023.             }
  2024.         }
  2025.  
  2026.         return threePoints?
  2027.                     new double[] { x0, y0, x2, y2, x1, y1 } :
  2028.                     new double[] { x0, y0, x1, y1 };
  2029.     }
  2030.  
  2031.  
  2032.     private static GeneralPath pathToShape(double[] path, boolean close) {
  2033.         GeneralPath result = new GeneralPath(GeneralPath.EVEN_ODD, path.length);
  2034.         result.moveTo((float)path[0], (float)path[1]);
  2035.         for (int i = 2; i < path.length; i += 2) {
  2036.             result.lineTo((float)path[i], (float)path[i+1]);
  2037.         }
  2038.         if (close) {
  2039.             result.closePath();
  2040.         }
  2041.  
  2042.         return result;
  2043.     }
  2044.  
  2045.     public Shape getCaretShape(TextHitInfo hit, Rectangle2D bounds) {
  2046.  
  2047.         checkTextHit(hit);
  2048.  
  2049.         if (bounds == null) {
  2050.             throw new IllegalArgumentException("Null Rectangle2D passed to TextLayout.getCaret()");
  2051.         }
  2052.  
  2053.         int hitCaret = hitToCaret(hit);
  2054.         GeneralPath hitShape =
  2055.                     pathToShape(getCaretPath(hitCaret, bounds, false), false);
  2056.  
  2057.         //return new Highlight(hitShape, true);
  2058.         return hitShape;
  2059.     }
  2060.  
  2061.     public Shape getCaretShape(TextHitInfo hit) {
  2062.  
  2063.         return getCaretShape(hit, getNaturalBounds());
  2064.     }
  2065.  
  2066.     /**
  2067.      * Return the "stronger" of the TextHitInfos.  The TextHitInfos
  2068.      * should be logical or visual counterparts.  They are not
  2069.      * checked for validity.
  2070.      */
  2071.     private final TextHitInfo getStrongHit(TextHitInfo hit1, TextHitInfo hit2) {
  2072.  
  2073.         // right now we're using the following rule for strong hits:
  2074.         // A hit on a character whose direction matches the line direction
  2075.         // is stronger than one on a character running opposite the
  2076.         // line direction.
  2077.         // If this rule ties, the hit on the leading edge of a character wins.
  2078.         // If THIS rule ties, hit1 wins.  Both rules shouldn't tie, unless the
  2079.         // infos aren't counterparts of some sort.
  2080.  
  2081.         boolean hit1Ltr, hit2Ltr;
  2082.  
  2083.         GlyphIterator iter = null;
  2084.  
  2085.         if (hit1.getCharIndex() == characterCount || hit1.getCharIndex() == -1) {
  2086.             hit1Ltr = isDirectionLTR;
  2087.         }
  2088.         else {
  2089.             if (iter == null) {
  2090.                 iter = createGlyphIterator();
  2091.             }
  2092.             iter.setLogicalGlyph(hit1.getCharIndex());
  2093.             hit1Ltr = (iter.glyphLevel() & 0x1) == 0;
  2094.         }
  2095.  
  2096.         if (hit1Ltr == isDirectionLTR && hit1.isLeadingEdge()) {
  2097.             return hit1;
  2098.         }
  2099.  
  2100.         if (hit2.getCharIndex() == characterCount || hit2.getCharIndex() == -1) {
  2101.             hit2Ltr = isDirectionLTR;
  2102.         }
  2103.         else {
  2104.             if (iter == null) {
  2105.                 iter = createGlyphIterator();
  2106.             }
  2107.             iter.setLogicalGlyph(hit2.getCharIndex());
  2108.             hit2Ltr = (iter.glyphLevel() & 0x1) == 0;
  2109.         }
  2110.  
  2111.         if (hit1Ltr == hit2Ltr) {
  2112.             if (!hit2.isLeadingEdge()) {
  2113.                 return hit1;
  2114.             }
  2115.             else {
  2116.                 return (hit1.isLeadingEdge())? hit1 : hit2;
  2117.             }
  2118.         }
  2119.         else {
  2120.             if (hit1Ltr == isDirectionLTR) {
  2121.                 return hit1;
  2122.             }
  2123.             else {
  2124.                 return hit2;
  2125.             }
  2126.         }
  2127.     }
  2128.  
  2129.     /**
  2130.      * Return the level of the character at index.  Indices -1 and
  2131.      * characterCount are assigned the base level of the layout.
  2132.      */
  2133.     public byte getCharacterLevel(int index) {
  2134.  
  2135.         // hmm, allow indices at endpoints?  For now, yes.
  2136.         if (index == -1 || index == characterCount) {
  2137.              return (byte) (isDirectionLTR? 0 : 1);
  2138.         }
  2139.  
  2140.         if (index < 0 || index >= characterCount) {
  2141.             throw new IllegalArgumentException("Index is out of range in getCharacterLevel.");
  2142.         }
  2143.  
  2144.         GlyphIterator iter = createGlyphIterator();
  2145.         iter.setLogicalGlyph(index);
  2146.         return iter.glyphLevel();
  2147.     }
  2148.  
  2149.     /**
  2150.      * Return two paths corresponding to the strong and weak caret.
  2151.      *
  2152.      * @param offset an offset in the layout
  2153.      * @param bounds the bounds to which to extend the carets
  2154.      * @return an array of two paths.  Element zero is the strong caret
  2155.      * (if any) or null.  Element one is the weak caret (if any) or null.
  2156.      * Element 0 and element 1 are never both null.
  2157.      */
  2158.     public Shape[] getCaretShapes(int offset, Rectangle2D bounds, CaretPolicy policy) {
  2159.  
  2160.         ensureCache();
  2161.  
  2162.         if (offset < 0 || offset > characterCount) {
  2163.             throw new IllegalArgumentException("Offset out of bounds in TextLayout.getCarets()");
  2164.         }
  2165.  
  2166.         if (bounds == null) {
  2167.             throw new IllegalArgumentException("Null Rectangle2D passed to TextLayout.getCarets()");
  2168.         }
  2169.  
  2170.         if (policy == null) {
  2171.             throw new IllegalArgumentException("Null CaretPolicy passed to TextLayout.getCarets()");
  2172.         }
  2173.  
  2174.         Shape[] result = new Shape[2];
  2175.  
  2176.         TextHitInfo hit = TextHitInfo.afterOffset(offset);
  2177.  
  2178.         int hitCaret = hitToCaret(hit);
  2179.         Shape hitShape = 
  2180.                     pathToShape(getCaretPath(hitCaret, bounds, false), false);
  2181.  
  2182.         TextHitInfo otherHit = hit.getOtherHit();
  2183.         int otherCaret = hitToCaret(otherHit);
  2184.  
  2185.         if (hitCaret == otherCaret) {
  2186.             result[0] = hitShape;
  2187.         }
  2188.         else { // more than one caret
  2189.             Shape otherShape =
  2190.                 pathToShape(getCaretPath(otherCaret, bounds, false), false);
  2191.  
  2192.             TextHitInfo strongHit = policy.getStrongCaret(hit, otherHit, this);
  2193.             boolean hitIsStrong = strongHit.equals(hit);
  2194.  
  2195.             if (hitIsStrong) {// then other is weak
  2196.                 result[0] = hitShape;
  2197.                 result[1] = otherShape;
  2198.             }
  2199.             else {
  2200.                 result[0] = otherShape;
  2201.                 result[1] = hitShape;
  2202.             }
  2203.         }
  2204.  
  2205.         return result;
  2206.     }
  2207.  
  2208.     /**
  2209.      * A convenience overload that uses the default caret policy.
  2210.      */
  2211.     public Shape[] getCaretShapes(int offset, Rectangle2D bounds) {
  2212.         // {sfb} parameter checking is done in overloaded version
  2213.         return getCaretShapes(offset, bounds, DEFAULT_CARET_POLICY);
  2214.     }
  2215.  
  2216.     /**
  2217.      * A convenience overload that uses the natural bounds of the layout as
  2218.      * the bounds, and the default caret policy.
  2219.      */
  2220.     public Shape[] getCaretShapes(int offset) {
  2221.         // {sfb} parameter checking is done in overloaded version
  2222.         return getCaretShapes(offset, getNaturalBounds(), DEFAULT_CARET_POLICY);
  2223.     }
  2224.  
  2225.     // A utility to return a path enclosing the given path
  2226.     // Path0 must be left or top of path1
  2227.     // {jbr} no assumptions about size of path0, path1 anymore.
  2228.     private static GeneralPath boundingShape(double[] path0, double[] path1) {
  2229.  
  2230.         GeneralPath result = pathToShape(path0, false);
  2231.         for (int i = path1.length-2; i >=0; i -= 2) {
  2232.             result.lineTo((float)path1[i], (float)path1[i+1]);
  2233.         }
  2234.  
  2235.         result.closePath();
  2236.  
  2237.         return result;
  2238.     }
  2239.  
  2240.     // A utility to convert a pair of carets into a bounding path
  2241.     // {jbr} Shape is never outside of bounds.
  2242.     private GeneralPath caretBoundingShape(int caret0,
  2243.                                            int caret1,
  2244.                                            Rectangle2D bounds) {
  2245.  
  2246.         if (caret0 > caret1) {
  2247.             int temp = caret0;
  2248.             caret0 = caret1;
  2249.             caret1 = temp;
  2250.         }
  2251.  
  2252.         return boundingShape(getCaretPath(caret0, bounds, true),
  2253.                              getCaretPath(caret1, bounds, true));
  2254.     }
  2255.  
  2256.     /*
  2257.      * A utility to return the path bounding the area to the left (top) of the
  2258.      * layout.
  2259.      * Shape is never outside of bounds.
  2260.      */
  2261.     private GeneralPath leftShape(Rectangle2D bounds) {
  2262.  
  2263.         double[] path0;
  2264.         if (isVerticalLine) {
  2265.             path0 = new double[] { bounds.getX(), bounds.getY(),
  2266.                                        bounds.getX() + bounds.getWidth(),
  2267.                                        bounds.getY() };
  2268.         } else {
  2269.             path0 = new double[] { bounds.getX(),
  2270.                                        bounds.getY() + bounds.getHeight(),
  2271.                                        bounds.getX(), bounds.getY() };
  2272.         }
  2273.  
  2274.         double[] path1 = getCaretPath(0, bounds, true);
  2275.  
  2276.         return boundingShape(path0, path1);
  2277.     }
  2278.  
  2279.     /*
  2280.      * A utility to return the path bounding the area to the right (bottom) of
  2281.      * the layout.
  2282.      */
  2283.     private GeneralPath rightShape(Rectangle2D bounds) {
  2284.         double[] path1;
  2285.         if (isVerticalLine) {
  2286.             path1 = new double[] {
  2287.                 bounds.getX(),
  2288.                 bounds.getY() + bounds.getHeight(),
  2289.                 bounds.getX() + bounds.getWidth(),
  2290.                 bounds.getY() + bounds.getHeight()
  2291.             };
  2292.         } else {
  2293.             path1 = new double[] {
  2294.                 bounds.getX() + bounds.getWidth(),
  2295.                 bounds.getY() + bounds.getHeight(),
  2296.                 bounds.getX() + bounds.getWidth(),
  2297.                 bounds.getY()
  2298.             };
  2299.         }
  2300.  
  2301.         double[] path0 = getCaretPath(characterCount, bounds, true);
  2302.  
  2303.         return boundingShape(path0, path1);
  2304.     }
  2305.  
  2306.     /**
  2307.      * Return the logical ranges of text corresponding to a visual selection.
  2308.      *
  2309.      * @param firstEndpoint an endpoint of the visual range.
  2310.      * @param secondEndpoint the other enpoint of the visual range.  Can be less than
  2311.      * <code>firstEndpoint</code>.
  2312.      * @return an array of integers representing start/limit pairs for the
  2313.      * selected ranges
  2314.      *
  2315.      * @see #getVisualHighlight
  2316.      */
  2317.     public int[] getLogicalRangesForVisualSelection(TextHitInfo firstEndpoint,
  2318.                                                     TextHitInfo secondEndpoint) {
  2319.         ensureCache();
  2320.  
  2321.         checkTextHit(firstEndpoint);
  2322.         checkTextHit(secondEndpoint);
  2323.  
  2324.         // !!! probably want to optimize for all LTR text
  2325.  
  2326.         boolean[] included = new boolean[characterCount];
  2327.  
  2328.         GlyphIterator iter = createGlyphIterator();
  2329.  
  2330.         int startIndex = hitToCaret(firstEndpoint);
  2331.         int limitIndex = hitToCaret(secondEndpoint);
  2332.  
  2333.         if (startIndex > limitIndex) {
  2334.             int t = startIndex;
  2335.             startIndex = limitIndex;
  2336.             limitIndex = t;
  2337.         }
  2338.  
  2339.         /*
  2340.          * now we have the visual indexes of the glyphs at the start and limit
  2341.          * of the selection range walk through runs marking characters that
  2342.          * were included in the visual range there is probably a more efficient
  2343.          * way to do this, but this ought to work, so hey
  2344.          */
  2345.  
  2346.         if (startIndex < limitIndex) {
  2347.             iter.setVisualGlyph(startIndex);
  2348.             while (iter.isValid() && iter.visualIndex() < limitIndex) {
  2349.                 included[iter.logicalIndex()] = true;
  2350.                 iter.nextVisualGlyph();
  2351.             }
  2352.         }
  2353.  
  2354.         /*
  2355.          * count how many runs we have, ought to be one or two, but perhaps
  2356.          * things are especially weird
  2357.          */
  2358.         int count = 0;
  2359.         boolean inrun = false;
  2360.         for (int i = 0; i < characterCount; i++) {
  2361.             if (included[i] != inrun) {
  2362.                 inrun = !inrun;
  2363.                 if (inrun) {
  2364.                     count++;
  2365.                 }
  2366.             }
  2367.         }
  2368.  
  2369.         int[] ranges = new int[count * 2];
  2370.         count = 0;
  2371.         inrun = false;
  2372.         for (int i = 0; i < characterCount; i++) {
  2373.             if (included[i] != inrun) {
  2374.                 ranges[count++] = i;
  2375.                 inrun = !inrun;
  2376.             }
  2377.         }
  2378.         if (inrun) {
  2379.             ranges[count++] = characterCount;
  2380.         }
  2381.  
  2382.         return ranges;
  2383.     }
  2384.  
  2385.     /**
  2386.      * Return a path enclosing the visual selection in the given range,
  2387.      * extended to bounds.
  2388.      * <p>
  2389.      * If the selection includes the leftmost (topmost) position, the selection
  2390.      * is extended to the left (top) of the bounds.  If the selection includes
  2391.      * the rightmost (bottommost) position, the selection is extended to the
  2392.      * right (bottom) of the bounds.  The height (width on vertical lines) of
  2393.      * the selection is always extended to bounds.
  2394.      * <p>
  2395.      * Although the selection is always contiguous, the logically selected
  2396.      * text can be discontiguous on lines with mixed-direction text.  The
  2397.      * logical ranges of text selected can be retrieved using
  2398.      * getLogicalRangesForVisualSelection.  For example, consider the text
  2399.      * 'ABCdef' where capital letters indicate right-to-left text, rendered
  2400.      * on a right-to-left line, with a visual selection from 0L (the leading
  2401.      * edge of 'A') to 3T (the trailing edge of 'd').  The text appears as
  2402.      * follows, with bold underlined areas representing the selection:
  2403.      * <br><pre>
  2404.      *    d<u><b>efCBA  </b></u>
  2405.      * </pre>
  2406.      * The logical selection ranges are 0-3, 4-6 (ABC, ef) because the
  2407.      * visually contiguous text is logically discontiguous.  Also note that
  2408.      * since the rightmost position on the layout (to the right of 'A') is
  2409.      * selected, the selection is extended to the right of the bounds.
  2410.      *
  2411.      * @param firstEndpoint one end of the visual selection
  2412.      * @param secondEndpoint the other end of the visual selection
  2413.      * @param bounds the bounding rectangle to which to extend the selection
  2414.      * @return an area enclosing the selection
  2415.      *
  2416.      * @see #getLogicalRangesForVisualSelection
  2417.      * @see #getLogicalHighlight
  2418.      */
  2419.     public Shape getVisualHighlightShape(TextHitInfo firstEndpoint,
  2420.                                         TextHitInfo secondEndpoint,
  2421.                                         Rectangle2D bounds)
  2422.     {
  2423.         ensureCache();
  2424.  
  2425.         checkTextHit(firstEndpoint);
  2426.         checkTextHit(secondEndpoint);
  2427.  
  2428.         if(bounds == null) {
  2429.                 throw new IllegalArgumentException("Null Rectangle2D passed to TextLayout.getVisualHighlight()");
  2430.         }
  2431.  
  2432.         GeneralPath result = new GeneralPath(GeneralPath.EVEN_ODD);
  2433.  
  2434.         int firstCaret = hitToCaret(firstEndpoint);
  2435.         int secondCaret = hitToCaret(secondEndpoint);
  2436.  
  2437.         result.append(caretBoundingShape(firstCaret, secondCaret, bounds),
  2438.                       false);
  2439.  
  2440.         if (firstCaret == 0 || secondCaret == 0) {
  2441.             result.append(leftShape(bounds), false);
  2442.         }
  2443.  
  2444.         if (firstCaret == characterCount || secondCaret == characterCount) {
  2445.             result.append(rightShape(bounds), false);
  2446.         }
  2447.  
  2448.         //return new Highlight(result, false);
  2449.         return  result;
  2450.     }
  2451.  
  2452.     /**
  2453.      * A convenience overload which uses the natural bounds of the layout.
  2454.      */
  2455.     public Shape getVisualHighlightShape(TextHitInfo firstEndpoint,
  2456.                                              TextHitInfo secondEndpoint) {
  2457.         return getVisualHighlightShape(firstEndpoint, secondEndpoint, getNaturalBounds());
  2458.     }
  2459.  
  2460.     /**
  2461.      * Return a path enclosing the logical selection in the given range,
  2462.      * extended to bounds.
  2463.      * <p>
  2464.      * If the selection range includes the first logical character, the
  2465.      * selection is extended to the portion of bounds before the start of the
  2466.      * layout.  If the range includes the last logical character, the
  2467.      * selection is extended to the portion of bounds after the end of
  2468.      * the layout.  The height (width on vertical lines) of the selection is
  2469.      * always extended to bounds.
  2470.      * <p>
  2471.      * The selection can be discontiguous on lines with mixed-direction text.
  2472.      * Only those characters in the logical range between start and limit will
  2473.      * appear selected.  For example consider the text 'ABCdef' where capital
  2474.      * letters indicate right-to-left text, rendered on a right-to-left line,
  2475.      * with a logical selection from 0 to 4 ('ABCd').  The text appears as
  2476.      * follows, with bold standing in for the selection, and underlining for
  2477.      * the extension:
  2478.      * <br><pre>
  2479.      *    <u><b>d</b></u>ef<u><b>CBA  </b></u>
  2480.      * </pre>
  2481.      * The selection is discontiguous because the selected characters are
  2482.      * visually discontiguous. Also note that since the range includes the
  2483.      * first logical character (A), the selection is extended to the portion
  2484.      * of the bounds before the start of the layout, which in this case
  2485.      * (a right-to-left line) is the right portion of the bounds.
  2486.      *
  2487.      * @param firstEndpoint an endpoint in the range of characters to select
  2488.      * @param secondEndpoint the other endpoint of the range of characters
  2489.      * to select. Can be less than <code>firstEndpoint</code>.  The range
  2490.      * includes the character at min(firstEndpoint, secondEndpoint), but
  2491.      * excludes max(firstEndpoint, secondEndpoint).
  2492.      * @param bounds the bounding rectangle to which to extend the selection
  2493.      * @return an area enclosing the selection
  2494.      *
  2495.      * @see #getVisualHighlight
  2496.      */
  2497.     public Shape getLogicalHighlightShape(int firstEndpoint,
  2498.                                          int secondEndpoint,
  2499.                                          Rectangle2D bounds) {
  2500.         if (bounds == null) {
  2501.             throw new IllegalArgumentException("Null Rectangle2D passed to TextLayout.getLogicalHighlight()");
  2502.         }
  2503.  
  2504.         ensureCache();
  2505.  
  2506.         if (firstEndpoint > secondEndpoint) {
  2507.             int t = firstEndpoint;
  2508.             firstEndpoint = secondEndpoint;
  2509.             secondEndpoint = t;
  2510.         }
  2511.  
  2512.         if(firstEndpoint < 0 || secondEndpoint > characterCount) {
  2513.                 throw new IllegalArgumentException("Range is invalid in TextLayout.getLogicalHighlight()");
  2514.         }
  2515.  
  2516.         int[] carets = new int[10]; // would this ever not handle all cases?
  2517.         int count = 0;
  2518.  
  2519.         if (firstEndpoint < secondEndpoint) {
  2520.                 GlyphIterator iter = createGlyphIterator();
  2521.                 iter.setLogicalGlyph(firstEndpoint);
  2522.                     do {
  2523.                     carets[count++] = hitToCaret(TextHitInfo.leading(iter.logicalIndex()));
  2524.                 boolean ltr = iter.glyphIsLTR();
  2525.  
  2526.                         do {
  2527.                     iter.nextLogicalGlyph();
  2528.                     } while (iter.isValid() && iter.logicalIndex() < secondEndpoint && iter.glyphIsLTR() == ltr);
  2529.  
  2530.                 int hitCh = (iter.isValid())? iter.logicalIndex() : iter.limit();
  2531.                         carets[count++] = hitToCaret(TextHitInfo.trailing(hitCh - 1));
  2532.  
  2533.                         if (count == carets.length) {
  2534.                     int[] temp = new int[carets.length + 10];
  2535.                         System.arraycopy(carets, 0, temp, 0, count);
  2536.                         carets = temp;
  2537.                     }
  2538.                     } while (iter.isValid() && iter.logicalIndex() < secondEndpoint);
  2539.                 }
  2540.                 else {
  2541.                     count = 2;
  2542.                     carets[0] = carets[1] = hitToCaret(TextHitInfo.leading(firstEndpoint));
  2543.                 }
  2544.  
  2545.         // now create paths for pairs of carets
  2546.  
  2547.         GeneralPath result = new GeneralPath(GeneralPath.EVEN_ODD);
  2548.  
  2549.         for (int i = 0; i < count; i += 2) {
  2550.             result.append(caretBoundingShape(carets[i], carets[i+1], bounds),
  2551.                           false);
  2552.         }
  2553.  
  2554.         if ((isDirectionLTR && firstEndpoint == 0) || (!isDirectionLTR &&
  2555.                                                secondEndpoint == characterCount)) {
  2556.             result.append(leftShape(bounds), false);
  2557.         }
  2558.  
  2559.         if ((isDirectionLTR && secondEndpoint == characterCount) ||
  2560.             (!isDirectionLTR && firstEndpoint == 0)) {
  2561.             result.append(rightShape(bounds), false);
  2562.         }
  2563.  
  2564.         return result;
  2565.     }
  2566.  
  2567.     /**
  2568.      * A convenience overload which uses the natural bounds of the layout.
  2569.      */
  2570.     public Shape getLogicalHighlightShape(int firstEndpoint, int secondEndpoint) {
  2571.  
  2572.         return getLogicalHighlightShape(firstEndpoint, secondEndpoint, getNaturalBounds());
  2573.     }
  2574.  
  2575.     /**
  2576.      * Return the black box bounds of the characters in the given range.
  2577.      *
  2578.      * The black box bounds is an area consisting of the union of the bounding
  2579.      * boxes of all the glyphs corresponding to the characters between start
  2580.      * and limit.  This path may be disjoint.
  2581.      *
  2582.      * @param firstEndpoint one end of the character range
  2583.      * @param secondEndpoint the other end of the character range.  Can be
  2584.      * less than <code>firstEndpoint</code>.
  2585.      * @return a path enclosing the black box bounds
  2586.      */
  2587.     public Shape getBlackBoxBounds(int firstEndpoint, int secondEndpoint) {
  2588.         ensureCache();
  2589.  
  2590.         if (firstEndpoint > secondEndpoint) {
  2591.             int t = firstEndpoint;
  2592.             firstEndpoint = secondEndpoint;
  2593.             secondEndpoint = t;
  2594.         }
  2595.  
  2596.         if(firstEndpoint < 0 || secondEndpoint > characterCount) {
  2597.             throw new IllegalArgumentException("Invalid range passed to TextLayout.getBlackBoxBounds()");
  2598.         }
  2599.  
  2600.         /*
  2601.          * return an area that consists of the bounding boxes of all the
  2602.          * characters from firstEndpoint to limit
  2603.          */
  2604.  
  2605.         GeneralPath result = new GeneralPath(GeneralPath.EVEN_ODD);
  2606.  
  2607.         if (firstEndpoint < characterCount) {
  2608.             GlyphIterator iter = createGlyphIterator();
  2609.             for (iter.setLogicalGlyph(firstEndpoint);
  2610.                         iter.isValid() && iter.logicalIndex() < secondEndpoint;
  2611.                         iter.nextLogicalGlyph()) {
  2612.  
  2613.                 if (!iter.isWhitespace()) {
  2614.                     Rectangle2D r = iter.glyphBounds();
  2615.                     result.append(r, false);
  2616.                 }
  2617.             }
  2618.         }
  2619.  
  2620.         if (dx != 0 || dy != 0) {
  2621.             AffineTransform translate = new AffineTransform();
  2622.             translate.setToTranslation(dx, dy);
  2623.             result = (GeneralPath) result.createTransformedShape(translate);
  2624.         }
  2625.  
  2626.         //return new Highlight(result, false);
  2627.         return result;
  2628.     }
  2629.  
  2630.     /**
  2631.      * Accumulate the advances of the characters at and after startPos, until a
  2632.      * character is reached whose advance would equal or exceed width.
  2633.      * Return the index of that character.
  2634.      */
  2635.     int getLineBreakIndex(int startPos, float width) {
  2636.         ensureCache();
  2637.  
  2638.         int accChars = 0;
  2639.         for (int i = 0; i < glyphs.length; i++) {
  2640.             TextLayoutComponent set = glyphs[i];
  2641.             int numCharacters = set.getNumCharacters();
  2642.             if (startPos >= numCharacters) {
  2643.                 startPos -= numCharacters; // skip sets before startPos
  2644.             } else {
  2645.                 int possibleBreak = set.getLineBreakIndex(startPos, width);
  2646.                 if (possibleBreak < numCharacters) { // found it
  2647.                     return accChars + possibleBreak;
  2648.                 } else {
  2649.                     if (startPos == 0) {
  2650.                         width -= set.getAdvance();
  2651.                     } else {
  2652.                         width -= set.getAdvanceBetween(startPos, numCharacters);
  2653.                         /*
  2654.                          * start looking at the first character of the next
  2655.                          * glyph set
  2656.                          */
  2657.                         startPos = 0;
  2658.                     }
  2659.                 }
  2660.             }
  2661.             accChars += numCharacters;
  2662.         }
  2663.  
  2664.         return characterCount;
  2665.     }
  2666.  
  2667.     /**
  2668.      * Return the distance from the point (x, y) to the caret along the line
  2669.      * direction defined in caretInfo.  Distance is negative if the point is
  2670.      * to the left of the caret on a horizontal line, or above the caret on
  2671.      * a vertical line.
  2672.      * Utility for use by hitTestChar.
  2673.      */
  2674.     private float caretToPointDistance(float[] caretInfo, float x, float y) {
  2675.         // distanceOffBaseline is negative if you're 'above' baseline
  2676.  
  2677.         float lineDistance = isVerticalLine? y : x;
  2678.         float distanceOffBaseline = isVerticalLine? -x : y;
  2679.  
  2680.         return lineDistance - caretInfo[0] +
  2681.             (distanceOffBaseline*caretInfo[1]);
  2682.     }
  2683.  
  2684.  
  2685.     /**
  2686.      * Return a TextHitInfo corresponding to the point.
  2687.      *
  2688.      * Coordinates outside the bounds of the layout map to hits on the leading
  2689.      * edge of the first logical character, or the trailing edge of the last
  2690.      * logical character, as appropriate, regardless of the position of that
  2691.      * character in the line.  Only the direction along the baseline is used
  2692.      * to make this evaluation.
  2693.      *
  2694.      * @param x the x offset from the origin of the layout
  2695.      * @param y the y offset from the origin of the layout
  2696.      * @return a hit describing the character and edge (leading or trailing)
  2697.      * under the point
  2698.      */
  2699.  
  2700.     public TextHitInfo hitTestChar(float x, float y, Rectangle2D bounds) {
  2701.  
  2702.         ensureCache();
  2703.  
  2704.         int hitLB, hitUB;
  2705.  
  2706.         float[] caretInfo;
  2707.  
  2708.         hitLB = 0;
  2709.         caretInfo = getCaretInfo(hitLB, bounds);
  2710.  
  2711.         GlyphIterator iter = createGlyphIterator();
  2712.  
  2713.         if (caretToPointDistance(caretInfo, x, y) < 0) {
  2714.             return isDirectionLTR? TextHitInfo.trailing(-1) :
  2715.                             TextHitInfo.leading(characterCount);
  2716.         }
  2717.  
  2718.         hitUB = characterCount;
  2719.         caretInfo = getCaretInfo(hitUB, bounds);
  2720.  
  2721.         if (caretToPointDistance(caretInfo, x, y) >= 0) {
  2722.             return isDirectionLTR? TextHitInfo.leading(characterCount) :
  2723.                                                 TextHitInfo.trailing(-1);
  2724.         }
  2725.  
  2726.         while (true) {
  2727.  
  2728.             // if there are no valid caret positions between
  2729.             // hitLB and hitUB then exit this loop;  otherwise
  2730.             // set test to a valid caret position in the
  2731.             // interval (hitLB, hitUB)
  2732.  
  2733.             if (hitLB + 1 == hitUB)
  2734.                 break;
  2735.  
  2736.             int test = (hitLB + hitUB) / 2;
  2737.  
  2738.             iter.setVisualGlyph(test);
  2739.  
  2740.             while(!iterIsAtValidCaret(iter))
  2741.                 iter.nextVisualGlyph();
  2742.  
  2743.             test = iter.isValid()? iter.visualIndex() : characterCount;
  2744.  
  2745.             if (test == hitUB) {
  2746.                 // If we're here then there were no valid caret
  2747.                 // positions between the halfway point and the
  2748.                 // end of the test region.  Reset test and back
  2749.                 // up to a valid caret position.
  2750.  
  2751.                 iter.setVisualGlyph((hitLB + hitUB) / 2);
  2752.                 do {
  2753.                     iter.previousVisualGlyph();
  2754.                 } while (!iterIsAtValidCaret(iter));
  2755.  
  2756.                 test = iter.visualIndex();
  2757.  
  2758.                 if (test == hitLB)
  2759.                     break;
  2760.             }
  2761.  
  2762.  
  2763.             caretInfo = getCaretInfo(test, bounds);
  2764.  
  2765.             float caretDist = caretToPointDistance(caretInfo, x, y);
  2766.  
  2767.             if (caretDist == 0) {
  2768.                 // return a hit on the left side of the glyph at test
  2769.                 // test is a valid position, since it is less than characterCount
  2770.                 iter.setVisualGlyph(test);
  2771.                 int charIndex = iter.logicalIndex();
  2772.                 boolean leading = iter.glyphIsLTR();
  2773.                 return leading? TextHitInfo.leading(charIndex) :
  2774.                                         TextHitInfo.trailing(charIndex);
  2775.             }
  2776.             else if (caretDist < 0) {
  2777.                 hitUB = test;
  2778.             }
  2779.             else {
  2780.                 hitLB = test;
  2781.             }
  2782.         }
  2783.  
  2784.         // now hit char is either to the right of hitLB
  2785.         // or left of hitUB
  2786.  
  2787.         // make caretInfo be center of glyph:
  2788.         iter.setVisualGlyph(hitLB);
  2789.         float hitAdvance = isVerticalLine? iter.glyphYPosition() :
  2790.             iter.glyphXPosition()-dx;
  2791.  
  2792.         // performance note:  dividing by 2 in the following loop is
  2793.         // the best thing to do, since the loop will usually only go
  2794.         // one iteration, and almost never more than two iterations
  2795.         for (int i = hitLB; i < hitUB; i++) {
  2796.             if (i == hitUB-1) {
  2797.                 hitAdvance += iter.glyphAdvance()/2;
  2798.             } else {
  2799.                 hitAdvance += iter.distanceToNextGlyph()/2;
  2800.                 iter.nextVisualGlyph();
  2801.             }
  2802.         }
  2803.  
  2804.         caretInfo = new float[2];
  2805.         caretInfo[0] = hitAdvance;
  2806.         caretInfo[1] = iter.glyphAngle();
  2807.         if (caretInfo[1] != 0) {
  2808.             caretInfo[0] += caretInfo[1] *
  2809.                 (isVerticalLine?
  2810.                         iter.glyphXPosition() : iter.glyphYPosition());
  2811.         }
  2812.  
  2813.  
  2814.         float centerDist = caretToPointDistance(caretInfo, x, y);
  2815.  
  2816.         TextHitInfo rval;
  2817.  
  2818.         if (centerDist < 0) {
  2819.             iter.setVisualGlyph(hitLB);
  2820.             rval = iter.glyphIsLTR()? TextHitInfo.leading(iter.logicalIndex())
  2821.                                 : TextHitInfo.trailing(iter.logicalIndex());
  2822.         }
  2823.         else {
  2824.             if (hitUB < characterCount) {
  2825.                 iter.setVisualGlyph(hitUB);
  2826.                 iter.previousVisualGlyph();
  2827.  
  2828.                 boolean leading = !iter.glyphIsLTR();
  2829.                 rval = leading? TextHitInfo.leading(iter.logicalIndex()) :
  2830.                                     TextHitInfo.trailing(iter.logicalIndex());
  2831.             }
  2832.             else {
  2833.                 iter.setVisualGlyph(hitUB-1);
  2834.                 boolean leading = !iter.glyphIsLTR();
  2835.                 rval = leading? TextHitInfo.leading(iter.logicalIndex()) :
  2836.                                 TextHitInfo.trailing(iter.logicalIndex());
  2837.             }
  2838.         }
  2839.  
  2840.         return rval;
  2841.     }
  2842.  
  2843.     /**
  2844.      * A convenience overload which uses the natural bounds of the layout.
  2845.      */
  2846.     public TextHitInfo hitTestChar(float x, float y) {
  2847.  
  2848.         return hitTestChar(x, y, getNaturalBounds());
  2849.     }
  2850.  
  2851.     /**
  2852.      * Return a layout that represents a subsection of this layout.  The
  2853.      * number of characters  must be >= 1.  The original layout must not be
  2854.      * justified.  The new layout will apply the bidi 'line reordering' rules
  2855.      * to the text.
  2856.      *
  2857.      * @param firstEndpoint the index of the first character to use
  2858.      * @param limit the index past the last character to use
  2859.      * @return a new layout
  2860.      */
  2861.  
  2862.     // note:  eventually this method will be removed from TextLayout and an equivalent method
  2863.     // added to TextMeasurer.  For now, TextMeasurer is implemented using TextLayout, but it
  2864.     // should be the only client of this method.
  2865.     TextLayout sublayout(int start, int limit) {
  2866.         if (start < 0 || limit < start || characterCount < limit) {
  2867.             throw new IllegalArgumentException("Invalid rantge passed to TextLayout.sublayout()");
  2868.         }
  2869.  
  2870.         return new TextLayout(this, start, limit); // new subset
  2871.     }
  2872.  
  2873.     // === sublayout stuff: ///
  2874.  
  2875.     /**
  2876.      * Return a glyphset computed from the glyph iterator, up to limitIndex.
  2877.      * If we're getting trailing whitespace (whose direction is determined by
  2878.      * the Layout, not by the glyphset containing the whitespace) then
  2879.      * useOverrideLevel is true, and override level is 0x0 for ltr and 0x1 for
  2880.      * rtl.
  2881.      */
  2882.     private static TextLayoutComponent getNextSetAtIter(GlyphIterator sourceIter,
  2883.                                              int limitIndex) {
  2884.  
  2885.         int startIndex = sourceIter.logicalIndex();
  2886.         TextLayoutComponent currentSet = sourceIter.currentGlyphSet();
  2887.  
  2888.         int startPosInSet = startIndex - sourceIter.logicalStartOfCurrentSet();
  2889.  
  2890.         int limitPosInSet = startPosInSet + (limitIndex - startIndex);
  2891.  
  2892.         if (limitPosInSet > currentSet.getNumCharacters()) {
  2893.             limitPosInSet = currentSet.getNumCharacters();
  2894.         }
  2895.  
  2896.         TextLayoutComponent rval = currentSet.subset(startPosInSet, limitPosInSet);
  2897.  
  2898.         int setLimit = startIndex + (limitPosInSet-startPosInSet);
  2899.  
  2900.         if (setLimit < sourceIter.limit()) {
  2901.             sourceIter.setLogicalGlyph(setLimit);
  2902.         }
  2903.         else {
  2904.             sourceIter.invalidate();
  2905.         }
  2906.  
  2907.         return rval;
  2908.     }
  2909.  
  2910.  
  2911.     private static int[] addToIntArray(int[] old, int end) {
  2912.  
  2913.         int len = (old == null)? 1 : old.length + 1;
  2914.         int[] newArray = new int[len];
  2915.  
  2916.         if (old != null) {
  2917.             System.arraycopy(old, 0, newArray, 0, len-1);
  2918.         }
  2919.  
  2920.         newArray[len-1] = end;
  2921.  
  2922.         return newArray;
  2923.     }
  2924.  
  2925.     // for newest "sublayout" constructor:
  2926.     private static TextLayoutComponent[] addToSetArray(
  2927.                                         TextLayoutComponent[] setArray,
  2928.                                         TextLayoutComponent set) {
  2929.  
  2930.         int len = (setArray == null)? 1 : setArray.length + 1;
  2931.         TextLayoutComponent[] newArray = new TextLayoutComponent[len];
  2932.  
  2933.         if (setArray != null) {
  2934.             System.arraycopy(setArray, 0, newArray, 0, len-1);
  2935.         }
  2936.  
  2937.         newArray[len-1] = set;
  2938.  
  2939.         return newArray;
  2940.     }
  2941.  
  2942.     private TextLayout(TextLayout source, final int start, final int limit) {
  2943.  
  2944.         baseline = source.baseline;
  2945.         baselineOffsets = new float[source.baselineOffsets.length];
  2946.         System.arraycopy(source.baselineOffsets, 0, baselineOffsets, 0, baselineOffsets.length);
  2947.         isDirectionLTR = source.isDirectionLTR;
  2948.         isVerticalLine = source.isVerticalLine;
  2949.         justifyRatio = source.justifyRatio;
  2950.  
  2951.         characterCount = limit - start;
  2952.  
  2953.         dx = dy = 0;
  2954.  
  2955.         TextLayoutComponent[] newSets = null;
  2956.         int[] newOrder = null;
  2957.  
  2958.         {
  2959.             GlyphIterator sourceIter = source.createGlyphIterator();
  2960.  
  2961.             // is there trailing whitespace to float to the end ?
  2962.             sourceIter.setLogicalGlyph(limit-1);
  2963.             while (sourceIter.isValid() && sourceIter.isWhitespace()) {
  2964.                 sourceIter.previousLogicalGlyph();
  2965.             }
  2966.  
  2967.             int whitespaceStart;
  2968.  
  2969.             if (sourceIter.isValid()) {
  2970.                 if (sourceIter.glyphIsLTR() != isDirectionLTR) {
  2971.                     whitespaceStart = sourceIter.logicalIndex() + 1;
  2972.                 } else {
  2973.                     whitespaceStart = limit;
  2974.                 }
  2975.             } else {
  2976.                 whitespaceStart = start;
  2977.             }
  2978.  
  2979.             sourceIter.setLogicalGlyph(start);
  2980.  
  2981.             int tempOrder;
  2982.             int minOrder = Integer.MAX_VALUE, maxOrder = Integer.MIN_VALUE;
  2983.  
  2984.             while (sourceIter.isValid() &&
  2985.                    sourceIter.logicalIndex() < whitespaceStart) {
  2986.  
  2987.                 tempOrder = sourceIter.visualIndex();
  2988.                 if (tempOrder < minOrder) {
  2989.                     minOrder = tempOrder;
  2990.                 }
  2991.                 if (tempOrder > maxOrder) {
  2992.                     maxOrder = tempOrder;
  2993.                 }
  2994.  
  2995.                 TextLayoutComponent nextSet =
  2996.                         getNextSetAtIter(sourceIter, whitespaceStart);
  2997.  
  2998.                 newSets = addToSetArray(newSets, nextSet);
  2999.                 newOrder = addToIntArray(newOrder, tempOrder);
  3000.             }
  3001.  
  3002.             while (sourceIter.isValid() && sourceIter.logicalIndex() < limit) {
  3003.  
  3004.                 tempOrder = isDirectionLTR? (++maxOrder) : (--minOrder);
  3005.  
  3006.                 TextLayoutComponent nextSet = getNextSetAtIter(sourceIter, limit);
  3007.                 nextSet = nextSet.setDirection(isDirectionLTR);
  3008.  
  3009.                 newSets = addToSetArray(newSets, nextSet);
  3010.                 newOrder = addToIntArray(newOrder, tempOrder);
  3011.             }
  3012.         }
  3013.  
  3014.         newOrder = GlyphSet.getContiguousOrder(newOrder);
  3015.  
  3016.         glyphs = newSets;
  3017.         glyphsOrder = GlyphSet.getInverseOrder(newOrder);
  3018.     }
  3019.  
  3020.    /**
  3021.     * Used for insert/delete char editing.  Clone this TextLayout and replace
  3022.     * oldSet with newSet in clone.
  3023.     */
  3024.     private TextLayout cloneAndReplaceSet(TextLayoutComponent oldSet,
  3025.                                           TextLayoutComponent newSet) {
  3026.  
  3027.         TextLayout newLayout = (TextLayout) this.clone();
  3028.  
  3029.         // deep-copy glyphs array, since it will change:
  3030.         newLayout.glyphs = new TextLayoutComponent[glyphs.length];
  3031.         System.arraycopy(glyphs, 0, newLayout.glyphs, 0, glyphs.length);
  3032.  
  3033.         TextLayoutComponent[] newSets = newLayout.glyphs;
  3034.  
  3035.         // find oldSet:
  3036.         int i;
  3037.         for (i=0; i<newSets.length; i++)
  3038.             if (newSets[i] == oldSet) {
  3039.                 newSets[i] = newSet;
  3040.                 break;
  3041.             }
  3042.  
  3043.         if (i == newSets.length) {
  3044.             throw new Error("Didn't find oldSet in cloneAndReplaceSet.");
  3045.         }
  3046.  
  3047.         newLayout.protoIterator = null;
  3048.         // newLayout.buildCache();
  3049.  
  3050.         return newLayout;
  3051.     }
  3052.  
  3053.     /**
  3054.      * An optimization to facilitate inserting single characters into a
  3055.      * paragraph.
  3056.      *
  3057.      * @param newParagraph the complete text for the new layout. This
  3058.      * represents the text after the insertion
  3059.      * occurred, restricted to the text which will go in the new layout.
  3060.      * @param insertPos the position, relative to the text (not the start of
  3061.      * the layout), at which the single character was inserted.
  3062.      * @return a new layout representing newParagraph
  3063.      */
  3064.     TextLayout insertChar(AttributedCharacterIterator newParagraph,
  3065.                           int textInsertPos)
  3066.     {
  3067.         ensureCache();
  3068.  
  3069.         if (newParagraph == null) {
  3070.             throw new IllegalArgumentException("Null AttributedCharacterIterator passed to TextLayout.insertChar().");
  3071.         }
  3072.  
  3073.         int newCharacterCount = characterCount+1;
  3074.         if (newParagraph.getEndIndex() - newParagraph.getBeginIndex() !=
  3075.             newCharacterCount) {
  3076.             throw new IllegalArgumentException("TextLayout.insertChar() only handles inserting a single character.");
  3077.         }
  3078.  
  3079.         if (textInsertPos < newParagraph.getBeginIndex() ||
  3080.             textInsertPos >= newParagraph.getEndIndex()) {
  3081.             throw new IllegalArgumentException("insertPos is out of range in TextLayout.insertChar().");
  3082.         }
  3083.  
  3084.         int insertPos = textInsertPos - newParagraph.getBeginIndex();
  3085.  
  3086.         TextLayoutComponent changeSet;
  3087.         // the offset in newParagraph where changeSet's text begins
  3088.         int setStartInText;
  3089.  
  3090.         // if insertPos is on a glyphset boundary, we'll try to append to the
  3091.         // previous set instead of inserting into first position in next set
  3092.  
  3093.         if (insertPos == characterCount) {
  3094.             changeSet = glyphs[glyphs.length-1];
  3095.             setStartInText = newParagraph.getBeginIndex() +
  3096.                 characterCount - changeSet.getNumCharacters();
  3097.         } else {
  3098.             GlyphIterator iter = createGlyphIterator();
  3099.             iter.setLogicalGlyph(insertPos==0? 0 : insertPos-1);
  3100.  
  3101.             changeSet = iter.currentGlyphSet();
  3102.             setStartInText = newParagraph.getBeginIndex() +
  3103.                 iter.logicalStartOfCurrentSet();
  3104.         }
  3105.  
  3106.         {
  3107.             boolean doItFromScratch = false;
  3108.  
  3109.             // check style compatability - if style run at textInsertPos doesn't
  3110.             // start at textInsertPos then styles are compatible
  3111.  
  3112.             newParagraph.setIndex(textInsertPos);
  3113.             if (textInsertPos == newParagraph.getBeginIndex()) {
  3114.                 if (newParagraph.getRunLimit() <= textInsertPos + 1) {
  3115.                     doItFromScratch = true;
  3116.                 }
  3117.             } else {
  3118.                 if (newParagraph.getRunStart() == textInsertPos) {
  3119.                     doItFromScratch = true;
  3120.                 }
  3121.             }
  3122.  
  3123.             if (doItFromScratch) {
  3124.                 return new TextLayout(newParagraph);
  3125.             }
  3126.         }
  3127.  
  3128.         boolean rerunBidi;
  3129.  
  3130.         if (!isDirectionLTR || !changeSet.isCompletelyLTR()) {
  3131.             rerunBidi = true;
  3132.         }
  3133.         else {
  3134.             // check dir class of inserted character
  3135.             char insertedChar = newParagraph.
  3136.                 setIndex(newParagraph.getBeginIndex() + insertPos);
  3137.  
  3138.             // insertedChar must be ltr in this context
  3139.  
  3140.             byte dirClass = IncrementalBidi.getDirectionClass(insertedChar);
  3141.             rerunBidi = dirClass == IncrementalBidi.R;
  3142.         }
  3143.  
  3144.         byte[] levels = null;
  3145.         int[] logicalOrdering = null;
  3146.  
  3147.         if (rerunBidi) {
  3148.             return new TextLayout(newParagraph);
  3149.             //IncrementalBidi bidi = new BidiInfo(newParagraph, isDirectionLTR? 0x0 : 0x1, null, null);
  3150.             //levels = bidi.createLevels();
  3151.             //int[] temp = bidi.createVisualToLogicalOrdering();
  3152.             //logicalOrdering = GlyphSet.getInverseOrder(temp);
  3153.         }
  3154.  
  3155.         int setLimitInText = setStartInText + changeSet.getNumCharacters() + 1;
  3156.  
  3157.         TextLayoutComponent newSet = changeSet.insertChar(newParagraph,
  3158.                                                setStartInText, setLimitInText,
  3159.                                                textInsertPos, logicalOrdering,
  3160.                                                levels);
  3161.  
  3162.         TextLayout result = cloneAndReplaceSet(changeSet, newSet);
  3163.  
  3164.         /*
  3165.          * no need to let font modify previous glyphset; since we always
  3166.          * append, the only way the first glyph of a set gets changed is if
  3167.          * the set is first in the layout
  3168.          */
  3169.  
  3170.         if (setLimitInText == (textInsertPos-1) &&
  3171.             setLimitInText < newParagraph.getRunLimit()) {
  3172.             // need to let font have a chance to modify following GlyphSet
  3173.             GlyphIterator iter = createGlyphIterator();
  3174.             iter.setLogicalGlyph(setLimitInText-newParagraph.getRunStart()-1);
  3175.  
  3176.             TextLayoutComponent nextSet = iter.currentGlyphSet();
  3177.             int nextStartInText = newParagraph.getBeginIndex() +
  3178.                 iter.logicalStartOfCurrentSet() + 1;
  3179.  
  3180.             TextLayoutComponent otherSet2 = nextSet.reshape(newParagraph,
  3181.                                                  nextStartInText,
  3182.                                                  nextStartInText +
  3183.                                                  nextSet.getNumCharacters(),
  3184.                                                  textInsertPos,
  3185.                                                  logicalOrdering, levels);
  3186.  
  3187.             if (nextSet != otherSet2) {
  3188.                 result = result.cloneAndReplaceSet(nextSet, otherSet2);
  3189.             }
  3190.         }
  3191.  
  3192.         /*
  3193.          * now we don't always call buildCache on result, so cc isn't updated
  3194.          * after clone
  3195.          */
  3196.         result.characterCount = newCharacterCount;
  3197.  
  3198.         return result;
  3199.     }
  3200.  
  3201.     /**
  3202.      * An optimization to facilitate deleting single characters from a
  3203.      * paragraph.
  3204.      *
  3205.      * @param newParagraph the complete text for the new paragraph.  This
  3206.      * represents the text after the deletion occurred, restricted to the
  3207.      * text which will go in the new layout.
  3208.      * @param textDeletePos the position, relative to the text (not the start
  3209.      * of the layout), at which the character was deleted.
  3210.      * @return a new layout representing newParagraph
  3211.      */
  3212.     TextLayout deleteChar(AttributedCharacterIterator newParagraph,
  3213.                           int textDeletePos) {
  3214.         ensureCache();
  3215.  
  3216.         if(newParagraph == null) {
  3217.             throw new IllegalArgumentException("Null AttributedCharacterIterator passed to TextLayout.deleteChar().");
  3218.         }
  3219.  
  3220.         int newCharacterCount = characterCount - 1;
  3221.         if (newParagraph.getEndIndex() - newParagraph.getBeginIndex() !=
  3222.             newCharacterCount)
  3223.         {
  3224.             throw new IllegalArgumentException("TextLayout.deleteChar() only handles deleting a single character.");
  3225.         }
  3226.  
  3227.         int deletePos = textDeletePos - newParagraph.getBeginIndex();
  3228.  
  3229.         TextLayoutComponent changeSet;
  3230.         int setStartInText; // the offset in newParagraph where changeSet begins
  3231.  
  3232.         GlyphIterator iter = createGlyphIterator();
  3233.         iter.setLogicalGlyph(deletePos);
  3234.  
  3235.         changeSet = iter.currentGlyphSet();
  3236.         setStartInText = newParagraph.getBeginIndex() +
  3237.             iter.logicalStartOfCurrentSet();
  3238.  
  3239.         // if we're deleting and entire glyphset, just redo from scratch:
  3240.         if (changeSet.getNumCharacters() == 1) {
  3241.             return new TextLayout(newParagraph);
  3242.         }
  3243.  
  3244.         // now check to see if we need to rerun bidi:
  3245.         int[] logicalOrdering = null;
  3246.         byte[] levels = null;
  3247.  
  3248.         if (!isDirectionLTR || !changeSet.isCompletelyLTR()) {
  3249.             return new TextLayout(newParagraph);
  3250.             //BidiInfo bidi = new BidiInfo(newParagraph, isDirectionLTR? 0x0 : 0x1, null, null);
  3251.             //levels = bidi.createLevels();
  3252.             //int[] temp = bidi.createVisualToLogicalOrdering();
  3253.             //logicalOrdering = GlyphSet.getInverseOrder(temp);
  3254.         }
  3255.  
  3256.         int setLimitInText = setStartInText + changeSet.getNumCharacters() - 1;
  3257.  
  3258.         TextLayoutComponent newSet = changeSet.deleteChar(newParagraph,
  3259.                                                setStartInText, setLimitInText,
  3260.                                                textDeletePos, logicalOrdering,
  3261.                                                levels);
  3262.  
  3263.         TextLayout result = cloneAndReplaceSet(changeSet, newSet);
  3264.  
  3265.         if (setStartInText == textDeletePos &&
  3266.             setStartInText > newParagraph.getBeginIndex())
  3267.         {
  3268.  
  3269.             GlyphIterator iter2 = createGlyphIterator();
  3270.             iter2.setLogicalGlyph(setStartInText -
  3271.                                   newParagraph.getBeginIndex() - 1);
  3272.  
  3273.             TextLayoutComponent previousSet = iter2.currentGlyphSet();
  3274.             int prevStartInText = newParagraph.getBeginIndex() +
  3275.                 iter2.logicalStartOfCurrentSet();
  3276.  
  3277.             TextLayoutComponent otherSet = previousSet.reshape(newParagraph,
  3278.                                                     prevStartInText,
  3279.                                                     setStartInText,
  3280.                                                     textDeletePos,
  3281.                                                     logicalOrdering,
  3282.                                                     levels);
  3283.  
  3284.             if (previousSet != otherSet) {
  3285.                 result = result.cloneAndReplaceSet(previousSet, otherSet);
  3286.             }
  3287.         }
  3288.  
  3289.         if (setLimitInText == textDeletePos &&
  3290.             setLimitInText < newParagraph.getEndIndex()) {
  3291.  
  3292.             GlyphIterator iter2 = createGlyphIterator();
  3293.             iter2.setLogicalGlyph(setLimitInText -
  3294.                                   newParagraph.getBeginIndex() + 1);
  3295.  
  3296.             TextLayoutComponent nextSet = iter2.currentGlyphSet();
  3297.             int nextStartInText = setLimitInText;
  3298.  
  3299.             TextLayoutComponent otherSet2 = nextSet.reshape(newParagraph,
  3300.                                                  nextStartInText,
  3301.                                                  nextStartInText +
  3302.                                                  nextSet.getNumCharacters(),
  3303.                                                  textDeletePos-1,
  3304.                                                  logicalOrdering, levels);
  3305.  
  3306.             if (nextSet != otherSet2) {
  3307.                 result = result.cloneAndReplaceSet(nextSet, otherSet2);
  3308.             }
  3309.         }
  3310.  
  3311.         /*
  3312.          * now we don't always call buildCache on result, so cc isn't
  3313.          * updated after clone
  3314.          */
  3315.         result.characterCount = newCharacterCount;
  3316.  
  3317.         return result;
  3318.     }
  3319.  
  3320.     /**
  3321.      * Return the hash code of this layout.
  3322.      */
  3323.     public int hashCode() {
  3324.         if (hashCodeCache == 0) {
  3325.             hashCodeCache = (glyphs.length << 16) ^
  3326.                 (glyphs[0].hashCode() << 3) ^ characterCount;
  3327.         }
  3328.         return hashCodeCache;
  3329.     }
  3330.  
  3331.     /**
  3332.      * Return true if the object is a TextLayout and this equals the object.
  3333.      */
  3334.     public boolean equals(Object obj) {
  3335.         return (obj instanceof TextLayout) && equals((TextLayout)obj);
  3336.     }
  3337.  
  3338.     /**
  3339.      * Return true if the two layouts are equal.
  3340.      *
  3341.      * Two layouts are equal if they contain equal glyphsets in the same order.
  3342.      *
  3343.      * @param layout the layout to which to compare this layout.
  3344.      */
  3345.     /*
  3346.      * !!! Does it make sense to implement equality for layouts?
  3347.      * I don't see why you'd want to do this.  Stephen's line layout used to
  3348.      * use it to see when it is safe to stop justifying lines, but I don't
  3349.      * think it's required for that, and the implementation here may not quite
  3350.      * work as it doesn't compare attributes or baselines except as through the
  3351.      * glyphsets.
  3352.      */
  3353.     public boolean equals(TextLayout layout) {
  3354.         if (layout == null) {
  3355.             return false;
  3356.         }
  3357.         if (layout == this) {
  3358.             return true;
  3359.         }
  3360.         if (glyphs.length != layout.glyphs.length) {
  3361.             return false;
  3362.         }
  3363.  
  3364.         for (int i = 0; i < glyphs.length; i++) {
  3365.             if (glyphs[i].hashCode() != layout.glyphs[i].hashCode()) {
  3366.                 return false;
  3367.             }
  3368.         }
  3369.  
  3370.         for (int i = 0; i < glyphs.length; i++) {
  3371.             if (!glyphs[i].equals(layout.glyphs[i])) {
  3372.                 return false;
  3373.             }
  3374.         }
  3375.  
  3376.         return true;
  3377.     }
  3378.  
  3379.     /**
  3380.      * Display the glyphsets contained in the layout, for debugging only.
  3381.      */
  3382.     public String toString() {
  3383.         StringBuffer buf = new StringBuffer();
  3384.  
  3385.         for (int i = 0; i < glyphs.length; i++) {
  3386.             buf.append(glyphs[i]);
  3387.         }
  3388.  
  3389.         buf.append("\n");
  3390.         return buf.toString();
  3391.      }
  3392.  
  3393.     /**
  3394.      * Render the layout at the provided location in the graphics.
  3395.      *
  3396.      * The origin of the layout is placed at x, y.  Rendering may touch any
  3397.      * point within getBounds() of this position.  This leaves the graphics
  3398.      * unchanged.
  3399.      *
  3400.      * @param g2 the graphics into which to render the layout
  3401.      * @param x the x position for the origin of the layout
  3402.      * @param y the y position for the origin of the layout
  3403.      * @see #getBounds
  3404.      */
  3405.     public void draw(Graphics2D g2, float x, float y) {
  3406.         if (g2 == null) {
  3407.             throw new IllegalArgumentException("Null Graphics2D passed to TextLayout.draw()");
  3408.         }
  3409.  
  3410.         float nx = x - dx;
  3411.         float ny = y - dy;
  3412.  
  3413.         for (int i = 0; i < glyphs.length; i++) {
  3414.             int vi = glyphsOrder == null ? i : glyphsOrder[i];
  3415.             TextLayoutComponent gs = glyphs[vi];
  3416.  
  3417.             float gx = isVerticalLine ? nx +
  3418.                 baselineOffsets[gs.getBaseline()] : nx;
  3419.             float gy = isVerticalLine ? ny : ny +
  3420.                 baselineOffsets[gs.getBaseline()];
  3421.  
  3422.             gs.draw(g2, gx, gy, this);
  3423.  
  3424.             if (isVerticalLine) {
  3425.                 ny += gs.getAdvance();
  3426.             } else {
  3427.                 nx += gs.getAdvance();
  3428.             }
  3429.         }
  3430.     }
  3431.  
  3432.     /**
  3433.      * Create an iterator over the glyphs in this layout.
  3434.      *
  3435.      * The iterator is used for accessing most information about a glyph,
  3436.      * including position, logical and physical index, font, metrics, and so on.
  3437.      *
  3438.      * This method is package-visible for testing.
  3439.      *
  3440.      * @see GlyphIterator
  3441.      */
  3442.     GlyphIterator createGlyphIterator() {
  3443.         if (protoIterator == null) {
  3444.             protoIterator = new GlyphIterator(this, glyphs, glyphsOrder);
  3445.         }
  3446.  
  3447.         return new GlyphIterator(protoIterator);
  3448.     }
  3449. }
  3450.