1 /*
   2  * Copyright (c) 1999, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing.text;
  26 
  27 import java.awt.*;
  28 import java.text.BreakIterator;
  29 import javax.swing.event.*;
  30 import java.util.BitSet;
  31 import java.util.Locale;
  32 
  33 import javax.swing.UIManager;
  34 import sun.swing.SwingUtilities2;
  35 import static sun.swing.SwingUtilities2.IMPLIED_CR;
  36 
  37 /**
  38  * A GlyphView is a styled chunk of text that represents a view
  39  * mapped over an element in the text model. This view is generally
  40  * responsible for displaying text glyphs using character level
  41  * attributes in some way.
  42  * An implementation of the GlyphPainter class is used to do the
  43  * actual rendering and model/view translations.  This separates
  44  * rendering from layout and management of the association with
  45  * the model.
  46  * <p>
  47  * The view supports breaking for the purpose of formatting.
  48  * The fragments produced by breaking share the view that has
  49  * primary responsibility for the element (i.e. they are nested
  50  * classes and carry only a small amount of state of their own)
  51  * so they can share its resources.
  52  * <p>
  53  * Since this view
  54  * represents text that may have tabs embedded in it, it implements the
  55  * <code>TabableView</code> interface.  Tabs will only be
  56  * expanded if this view is embedded in a container that does
  57  * tab expansion.  ParagraphView is an example of a container
  58  * that does tab expansion.
  59  *
  60  * @since 1.3
  61  *
  62  * @author  Timothy Prinzing
  63  */
  64 public class GlyphView extends View implements TabableView, Cloneable {
  65 
  66     /**
  67      * Constructs a new view wrapped on an element.
  68      *
  69      * @param elem the element
  70      */
  71     public GlyphView(Element elem) {
  72         super(elem);
  73         offset = 0;
  74         length = 0;
  75         Element parent = elem.getParentElement();
  76         AttributeSet attr = elem.getAttributes();
  77 
  78         //         if there was an implied CR
  79         impliedCR = (attr != null && attr.getAttribute(IMPLIED_CR) != null &&
  80         //         if this is non-empty paragraph
  81                    parent != null && parent.getElementCount() > 1);
  82         skipWidth = elem.getName().equals("br");
  83     }
  84 
  85     /**
  86      * Creates a shallow copy.  This is used by the
  87      * createFragment and breakView methods.
  88      *
  89      * @return the copy
  90      */
  91     protected final Object clone() {
  92         Object o;
  93         try {
  94             o = super.clone();
  95         } catch (CloneNotSupportedException cnse) {
  96             o = null;
  97         }
  98         return o;
  99     }
 100 
 101     /**
 102      * Fetch the currently installed glyph painter.
 103      * If a painter has not yet been installed, and
 104      * a default was not yet needed, null is returned.
 105      * @return the currently installed glyph painter
 106      */
 107     public GlyphPainter getGlyphPainter() {
 108         return painter;
 109     }
 110 
 111     /**
 112      * Sets the painter to use for rendering glyphs.
 113      * @param p the painter to use for rendering glyphs
 114      */
 115     public void setGlyphPainter(GlyphPainter p) {
 116         painter = p;
 117     }
 118 
 119     /**
 120      * Fetch a reference to the text that occupies
 121      * the given range.  This is normally used by
 122      * the GlyphPainter to determine what characters
 123      * it should render glyphs for.
 124      *
 125      * @param p0  the starting document offset &gt;= 0
 126      * @param p1  the ending document offset &gt;= p0
 127      * @return    the <code>Segment</code> containing the text
 128      */
 129      public Segment getText(int p0, int p1) {
 130          // When done with the returned Segment it should be released by
 131          // invoking:
 132          //    SegmentCache.releaseSharedSegment(segment);
 133          Segment text = SegmentCache.getSharedSegment();
 134          try {
 135              Document doc = getDocument();
 136              doc.getText(p0, p1 - p0, text);
 137          } catch (BadLocationException bl) {
 138              throw new StateInvariantError("GlyphView: Stale view: " + bl);
 139          }
 140          return text;
 141      }
 142 
 143     /**
 144      * Fetch the background color to use to render the
 145      * glyphs.  If there is no background color, null should
 146      * be returned.  This is implemented to call
 147      * <code>StyledDocument.getBackground</code> if the associated
 148      * document is a styled document, otherwise it returns null.
 149      * @return the background color to use to render the glyphs
 150      */
 151     public Color getBackground() {
 152         Document doc = getDocument();
 153         if (doc instanceof StyledDocument) {
 154             AttributeSet attr = getAttributes();
 155             if (attr.isDefined(StyleConstants.Background)) {
 156                 return ((StyledDocument)doc).getBackground(attr);
 157             }
 158         }
 159         return null;
 160     }
 161 
 162     /**
 163      * Fetch the foreground color to use to render the
 164      * glyphs.  If there is no foreground color, null should
 165      * be returned.  This is implemented to call
 166      * <code>StyledDocument.getBackground</code> if the associated
 167      * document is a StyledDocument.  If the associated document
 168      * is not a StyledDocument, the associated components foreground
 169      * color is used.  If there is no associated component, null
 170      * is returned.
 171      * @return the foreground color to use to render the glyphs
 172      */
 173     public Color getForeground() {
 174         Document doc = getDocument();
 175         if (doc instanceof StyledDocument) {
 176             AttributeSet attr = getAttributes();
 177             return ((StyledDocument)doc).getForeground(attr);
 178         }
 179         Component c = getContainer();
 180         if (c != null) {
 181             return c.getForeground();
 182         }
 183         return null;
 184     }
 185 
 186     /**
 187      * Fetch the font that the glyphs should be based
 188      * upon.  This is implemented to call
 189      * <code>StyledDocument.getFont</code> if the associated
 190      * document is a StyledDocument.  If the associated document
 191      * is not a StyledDocument, the associated components font
 192      * is used.  If there is no associated component, null
 193      * is returned.
 194      * @return the font that the glyphs should be based upon
 195      */
 196     public Font getFont() {
 197         Document doc = getDocument();
 198         if (doc instanceof StyledDocument) {
 199             AttributeSet attr = getAttributes();
 200             return ((StyledDocument)doc).getFont(attr);
 201         }
 202         Component c = getContainer();
 203         if (c != null) {
 204             return c.getFont();
 205         }
 206         return null;
 207     }
 208 
 209     /**
 210      * Determine if the glyphs should be underlined.  If true,
 211      * an underline should be drawn through the baseline.
 212      * @return if the glyphs should be underlined
 213      */
 214     public boolean isUnderline() {
 215         AttributeSet attr = getAttributes();
 216         return StyleConstants.isUnderline(attr);
 217     }
 218 
 219     /**
 220      * Determine if the glyphs should have a strikethrough
 221      * line.  If true, a line should be drawn through the center
 222      * of the glyphs.
 223      * @return if the glyphs should have a strikethrough line
 224      */
 225     public boolean isStrikeThrough() {
 226         AttributeSet attr = getAttributes();
 227         return StyleConstants.isStrikeThrough(attr);
 228     }
 229 
 230     /**
 231      * Determine if the glyphs should be rendered as superscript.
 232      * @return if the glyphs should be rendered as superscript
 233      */
 234     public boolean isSubscript() {
 235         AttributeSet attr = getAttributes();
 236         return StyleConstants.isSubscript(attr);
 237     }
 238 
 239     /**
 240      * Determine if the glyphs should be rendered as subscript.
 241      * @return if the glyphs should be rendered as subscript
 242      */
 243     public boolean isSuperscript() {
 244         AttributeSet attr = getAttributes();
 245         return StyleConstants.isSuperscript(attr);
 246     }
 247 
 248     /**
 249      * Fetch the TabExpander to use if tabs are present in this view.
 250      * @return the TabExpander to use if tabs are present in this view
 251      */
 252     public TabExpander getTabExpander() {
 253         return expander;
 254     }
 255 
 256     /**
 257      * Check to see that a glyph painter exists.  If a painter
 258      * doesn't exist, a default glyph painter will be installed.
 259      */
 260     protected void checkPainter() {
 261         if (painter == null) {
 262             if (defaultPainter == null) {
 263                 // the classname should probably come from a property file.
 264                 String classname = "javax.swing.text.GlyphPainter1";
 265                 try {
 266                     Class<?> c;
 267                     ClassLoader loader = getClass().getClassLoader();
 268                     if (loader != null) {
 269                         c = loader.loadClass(classname);
 270                     } else {
 271                         c = Class.forName(classname);
 272                     }
 273                     Object o = c.newInstance();
 274                     if (o instanceof GlyphPainter) {
 275                         defaultPainter = (GlyphPainter) o;
 276                     }
 277                 } catch (Throwable e) {
 278                     throw new StateInvariantError("GlyphView: Can't load glyph painter: "
 279                                                   + classname);
 280                 }
 281             }
 282             setGlyphPainter(defaultPainter.getPainter(this, getStartOffset(),
 283                                                       getEndOffset()));
 284         }
 285     }
 286 
 287     // --- TabableView methods --------------------------------------
 288 
 289     /**
 290      * Determines the desired span when using the given
 291      * tab expansion implementation.
 292      *
 293      * @param x the position the view would be located
 294      *  at for the purpose of tab expansion &gt;= 0.
 295      * @param e how to expand the tabs when encountered.
 296      * @return the desired span &gt;= 0
 297      * @see TabableView#getTabbedSpan
 298      */
 299     public float getTabbedSpan(float x, TabExpander e) {
 300         checkPainter();
 301 
 302         TabExpander old = expander;
 303         expander = e;
 304 
 305         if (expander != old) {
 306             // setting expander can change horizontal span of the view,
 307             // so we have to call preferenceChanged()
 308             preferenceChanged(null, true, false);
 309         }
 310 
 311         this.x = (int) x;
 312         int p0 = getStartOffset();
 313         int p1 = getEndOffset();
 314         float width = painter.getSpan(this, p0, p1, expander, x);
 315         return width;
 316     }
 317 
 318     /**
 319      * Determines the span along the same axis as tab
 320      * expansion for a portion of the view.  This is
 321      * intended for use by the TabExpander for cases
 322      * where the tab expansion involves aligning the
 323      * portion of text that doesn't have whitespace
 324      * relative to the tab stop.  There is therefore
 325      * an assumption that the range given does not
 326      * contain tabs.
 327      * <p>
 328      * This method can be called while servicing the
 329      * getTabbedSpan or getPreferredSize.  It has to
 330      * arrange for its own text buffer to make the
 331      * measurements.
 332      *
 333      * @param p0 the starting document offset &gt;= 0
 334      * @param p1 the ending document offset &gt;= p0
 335      * @return the span &gt;= 0
 336      */
 337     public float getPartialSpan(int p0, int p1) {
 338         checkPainter();
 339         float width = painter.getSpan(this, p0, p1, expander, x);
 340         return width;
 341     }
 342 
 343     // --- View methods ---------------------------------------------
 344 
 345     /**
 346      * Fetches the portion of the model that this view is responsible for.
 347      *
 348      * @return the starting offset into the model
 349      * @see View#getStartOffset
 350      */
 351     public int getStartOffset() {
 352         Element e = getElement();
 353         return (length > 0) ? e.getStartOffset() + offset : e.getStartOffset();
 354     }
 355 
 356     /**
 357      * Fetches the portion of the model that this view is responsible for.
 358      *
 359      * @return the ending offset into the model
 360      * @see View#getEndOffset
 361      */
 362     public int getEndOffset() {
 363         Element e = getElement();
 364         return (length > 0) ? e.getStartOffset() + offset + length : e.getEndOffset();
 365     }
 366 
 367     /**
 368      * Lazily initializes the selections field
 369      */
 370     private void initSelections(int p0, int p1) {
 371         int viewPosCount = p1 - p0 + 1;
 372         if (selections == null || viewPosCount > selections.length) {
 373             selections = new byte[viewPosCount];
 374             return;
 375         }
 376         for (int i = 0; i < viewPosCount; selections[i++] = 0);
 377     }
 378 
 379     /**
 380      * Renders a portion of a text style run.
 381      *
 382      * @param g the rendering surface to use
 383      * @param a the allocated region to render into
 384      */
 385     public void paint(Graphics g, Shape a) {
 386         checkPainter();
 387 
 388         boolean paintedText = false;
 389         Component c = getContainer();
 390         int p0 = getStartOffset();
 391         int p1 = getEndOffset();
 392         Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
 393         Color bg = getBackground();
 394         Color fg = getForeground();
 395 
 396         if (c != null && ! c.isEnabled()) {
 397             fg = (c instanceof JTextComponent ?
 398                 ((JTextComponent)c).getDisabledTextColor() :
 399                 UIManager.getColor("textInactiveText"));
 400         }
 401         if (bg != null) {
 402             g.setColor(bg);
 403             g.fillRect(alloc.x, alloc.y, alloc.width, alloc.height);
 404         }
 405         if (c instanceof JTextComponent) {
 406             JTextComponent tc = (JTextComponent) c;
 407             Highlighter h = tc.getHighlighter();
 408             if (h instanceof LayeredHighlighter) {
 409                 ((LayeredHighlighter)h).paintLayeredHighlights
 410                     (g, p0, p1, a, tc, this);
 411             }
 412         }
 413 
 414         if (Utilities.isComposedTextElement(getElement())) {
 415             Utilities.paintComposedText(g, a.getBounds(), this);
 416             paintedText = true;
 417         } else if(c instanceof JTextComponent) {
 418             JTextComponent tc = (JTextComponent) c;
 419             Color selFG = tc.getSelectedTextColor();
 420 
 421             if (// there's a highlighter (bug 4532590), and
 422                 (tc.getHighlighter() != null) &&
 423                 // selected text color is different from regular foreground
 424                 (selFG != null) && !selFG.equals(fg)) {
 425 
 426                 Highlighter.Highlight[] h = tc.getHighlighter().getHighlights();
 427                 if(h.length != 0) {
 428                     boolean initialized = false;
 429                     int viewSelectionCount = 0;
 430                     for (int i = 0; i < h.length; i++) {
 431                         Highlighter.Highlight highlight = h[i];
 432                         int hStart = highlight.getStartOffset();
 433                         int hEnd = highlight.getEndOffset();
 434                         if (hStart > p1 || hEnd < p0) {
 435                             // the selection is out of this view
 436                             continue;
 437                         }
 438                         if (!SwingUtilities2.useSelectedTextColor(highlight, tc)) {
 439                             continue;
 440                         }
 441                         if (hStart <= p0 && hEnd >= p1){
 442                             // the whole view is selected
 443                             paintTextUsingColor(g, a, selFG, p0, p1);
 444                             paintedText = true;
 445                             break;
 446                         }
 447                         // the array is lazily created only when the view
 448                         // is partially selected
 449                         if (!initialized) {
 450                             initSelections(p0, p1);
 451                             initialized = true;
 452                         }
 453                         hStart = Math.max(p0, hStart);
 454                         hEnd = Math.min(p1, hEnd);
 455                         paintTextUsingColor(g, a, selFG, hStart, hEnd);
 456                         // the array represents view positions [0, p1-p0+1]
 457                         // later will iterate this array and sum its
 458                         // elements. Positions with sum == 0 are not selected.
 459                         selections[hStart-p0]++;
 460                         selections[hEnd-p0]--;
 461 
 462                         viewSelectionCount++;
 463                     }
 464 
 465                     if (!paintedText && viewSelectionCount > 0) {
 466                         // the view is partially selected
 467                         int curPos = -1;
 468                         int startPos = 0;
 469                         int viewLen = p1 - p0;
 470                         while (curPos++ < viewLen) {
 471                             // searching for the next selection start
 472                             while(curPos < viewLen &&
 473                                     selections[curPos] == 0) curPos++;
 474                             if (startPos != curPos) {
 475                                 // paint unselected text
 476                                 paintTextUsingColor(g, a, fg,
 477                                         p0 + startPos, p0 + curPos);
 478                             }
 479                             int checkSum = 0;
 480                             // searching for next start position of unselected text
 481                             while (curPos < viewLen &&
 482                                     (checkSum += selections[curPos]) != 0) curPos++;
 483                             startPos = curPos;
 484                         }
 485                         paintedText = true;
 486                     }
 487                 }
 488             }
 489         }
 490         if(!paintedText)
 491             paintTextUsingColor(g, a, fg, p0, p1);
 492     }
 493 
 494     /**
 495      * Paints the specified region of text in the specified color.
 496      */
 497     final void paintTextUsingColor(Graphics g, Shape a, Color c, int p0, int p1) {
 498         // render the glyphs
 499         g.setColor(c);
 500         painter.paint(this, g, a, p0, p1);
 501 
 502         // render underline or strikethrough if set.
 503         boolean underline = isUnderline();
 504         boolean strike = isStrikeThrough();
 505         if (underline || strike) {
 506             // calculate x coordinates
 507             Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
 508             View parent = getParent();
 509             if ((parent != null) && (parent.getEndOffset() == p1)) {
 510                 // strip whitespace on end
 511                 Segment s = getText(p0, p1);
 512                 while (Character.isWhitespace(s.last())) {
 513                     p1 -= 1;
 514                     s.count -= 1;
 515                 }
 516                 SegmentCache.releaseSharedSegment(s);
 517             }
 518             int x0 = alloc.x;
 519             int p = getStartOffset();
 520             if (p != p0) {
 521                 x0 += (int) painter.getSpan(this, p, p0, getTabExpander(), x0);
 522             }
 523             int x1 = x0 + (int) painter.getSpan(this, p0, p1, getTabExpander(), x0);
 524 
 525             // calculate y coordinate
 526             int y = alloc.y + (int)(painter.getHeight(this) - painter.getDescent(this));
 527             if (underline) {
 528                 int yTmp = y + 1;
 529                 g.drawLine(x0, yTmp, x1, yTmp);
 530             }
 531             if (strike) {
 532                 // move y coordinate above baseline
 533                 int yTmp = y - (int) (painter.getAscent(this) * 0.3f);
 534                 g.drawLine(x0, yTmp, x1, yTmp);
 535             }
 536 
 537         }
 538     }
 539 
 540     /**
 541      * Determines the minimum span for this view along an axis.
 542      *
 543      * <p>This implementation returns the longest non-breakable area within
 544      * the view as a minimum span for {@code View.X_AXIS}.</p>
 545      *
 546      * @param axis  may be either {@code View.X_AXIS} or {@code View.Y_AXIS}
 547      * @return      the minimum span the view can be rendered into
 548      * @throws IllegalArgumentException if the {@code axis} parameter is invalid
 549      * @see         javax.swing.text.View#getMinimumSpan
 550      */
 551     @Override
 552     public float getMinimumSpan(int axis) {
 553         switch (axis) {
 554             case View.X_AXIS:
 555                 if (minimumSpan < 0) {
 556                     minimumSpan = 0;
 557                     int p0 = getStartOffset();
 558                     int p1 = getEndOffset();
 559                     while (p1 > p0) {
 560                         int breakSpot = getBreakSpot(p0, p1);
 561                         if (breakSpot == BreakIterator.DONE) {
 562                             // the rest of the view is non-breakable
 563                             breakSpot = p0;
 564                         }
 565                         minimumSpan = Math.max(minimumSpan,
 566                                 getPartialSpan(breakSpot, p1));
 567                         // Note: getBreakSpot returns the *last* breakspot
 568                         p1 = breakSpot - 1;
 569                     }
 570                 }
 571                 return minimumSpan;
 572             case View.Y_AXIS:
 573                 return super.getMinimumSpan(axis);
 574             default:
 575                 throw new IllegalArgumentException("Invalid axis: " + axis);
 576         }
 577     }
 578 
 579     /**
 580      * Determines the preferred span for this view along an
 581      * axis.
 582      *
 583      * @param axis may be either View.X_AXIS or View.Y_AXIS
 584      * @return   the span the view would like to be rendered into &gt;= 0.
 585      *           Typically the view is told to render into the span
 586      *           that is returned, although there is no guarantee.
 587      *           The parent may choose to resize or break the view.
 588      */
 589     public float getPreferredSpan(int axis) {
 590         if (impliedCR) {
 591             return 0;
 592         }
 593         checkPainter();
 594         int p0 = getStartOffset();
 595         int p1 = getEndOffset();
 596         switch (axis) {
 597         case View.X_AXIS:
 598             if (skipWidth) {
 599                 return 0;
 600             }
 601             return painter.getSpan(this, p0, p1, expander, this.x);
 602         case View.Y_AXIS:
 603             float h = painter.getHeight(this);
 604             if (isSuperscript()) {
 605                 h += h/3;
 606             }
 607             return h;
 608         default:
 609             throw new IllegalArgumentException("Invalid axis: " + axis);
 610         }
 611     }
 612 
 613     /**
 614      * Determines the desired alignment for this view along an
 615      * axis.  For the label, the alignment is along the font
 616      * baseline for the y axis, and the superclasses alignment
 617      * along the x axis.
 618      *
 619      * @param axis may be either View.X_AXIS or View.Y_AXIS
 620      * @return the desired alignment.  This should be a value
 621      *   between 0.0 and 1.0 inclusive, where 0 indicates alignment at the
 622      *   origin and 1.0 indicates alignment to the full span
 623      *   away from the origin.  An alignment of 0.5 would be the
 624      *   center of the view.
 625      */
 626     public float getAlignment(int axis) {
 627         checkPainter();
 628         if (axis == View.Y_AXIS) {
 629             boolean sup = isSuperscript();
 630             boolean sub = isSubscript();
 631             float h = painter.getHeight(this);
 632             float d = painter.getDescent(this);
 633             float a = painter.getAscent(this);
 634             float align;
 635             if (sup) {
 636                 align = 1.0f;
 637             } else if (sub) {
 638                 align = (h > 0) ? (h - (d + (a / 2))) / h : 0;
 639             } else {
 640                 align = (h > 0) ? (h - d) / h : 0;
 641             }
 642             return align;
 643         }
 644         return super.getAlignment(axis);
 645     }
 646 
 647     /**
 648      * Provides a mapping from the document model coordinate space
 649      * to the coordinate space of the view mapped to it.
 650      *
 651      * @param pos the position to convert &gt;= 0
 652      * @param a   the allocated region to render into
 653      * @param b   either <code>Position.Bias.Forward</code>
 654      *                or <code>Position.Bias.Backward</code>
 655      * @return the bounding box of the given position
 656      * @exception BadLocationException  if the given position does not represent a
 657      *   valid location in the associated document
 658      * @see View#modelToView
 659      */
 660     public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
 661         checkPainter();
 662         return painter.modelToView(this, pos, b, a);
 663     }
 664 
 665     /**
 666      * Provides a mapping from the view coordinate space to the logical
 667      * coordinate space of the model.
 668      *
 669      * @param x the X coordinate &gt;= 0
 670      * @param y the Y coordinate &gt;= 0
 671      * @param a the allocated region to render into
 672      * @param biasReturn either <code>Position.Bias.Forward</code>
 673      *  or <code>Position.Bias.Backward</code> is returned as the
 674      *  zero-th element of this array
 675      * @return the location within the model that best represents the
 676      *  given point of view &gt;= 0
 677      * @see View#viewToModel
 678      */
 679     public int viewToModel(float x, float y, Shape a, Position.Bias[] biasReturn) {
 680         checkPainter();
 681         return painter.viewToModel(this, x, y, a, biasReturn);
 682     }
 683 
 684     /**
 685      * Determines how attractive a break opportunity in
 686      * this view is.  This can be used for determining which
 687      * view is the most attractive to call <code>breakView</code>
 688      * on in the process of formatting.  The
 689      * higher the weight, the more attractive the break.  A
 690      * value equal to or lower than <code>View.BadBreakWeight</code>
 691      * should not be considered for a break.  A value greater
 692      * than or equal to <code>View.ForcedBreakWeight</code> should
 693      * be broken.
 694      * <p>
 695      * This is implemented to forward to the superclass for
 696      * the Y_AXIS.  Along the X_AXIS the following values
 697      * may be returned.
 698      * <dl>
 699      * <dt><b>View.ExcellentBreakWeight</b>
 700      * <dd>if there is whitespace proceeding the desired break
 701      *   location.
 702      * <dt><b>View.BadBreakWeight</b>
 703      * <dd>if the desired break location results in a break
 704      *   location of the starting offset.
 705      * <dt><b>View.GoodBreakWeight</b>
 706      * <dd>if the other conditions don't occur.
 707      * </dl>
 708      * This will normally result in the behavior of breaking
 709      * on a whitespace location if one can be found, otherwise
 710      * breaking between characters.
 711      *
 712      * @param axis may be either View.X_AXIS or View.Y_AXIS
 713      * @param pos the potential location of the start of the
 714      *   broken view &gt;= 0.  This may be useful for calculating tab
 715      *   positions.
 716      * @param len specifies the relative length from <em>pos</em>
 717      *   where a potential break is desired &gt;= 0.
 718      * @return the weight, which should be a value between
 719      *   View.ForcedBreakWeight and View.BadBreakWeight.
 720      * @see LabelView
 721      * @see ParagraphView
 722      * @see View#BadBreakWeight
 723      * @see View#GoodBreakWeight
 724      * @see View#ExcellentBreakWeight
 725      * @see View#ForcedBreakWeight
 726      */
 727     public int getBreakWeight(int axis, float pos, float len) {
 728         if (axis == View.X_AXIS) {
 729             checkPainter();
 730             int p0 = getStartOffset();
 731             int p1 = painter.getBoundedPosition(this, p0, pos, len);
 732             return p1 == p0 ? View.BadBreakWeight :
 733                    getBreakSpot(p0, p1) != BreakIterator.DONE ?
 734                             View.ExcellentBreakWeight : View.GoodBreakWeight;
 735         }
 736         return super.getBreakWeight(axis, pos, len);
 737     }
 738 
 739     /**
 740      * Breaks this view on the given axis at the given length.
 741      * This is implemented to attempt to break on a whitespace
 742      * location, and returns a fragment with the whitespace at
 743      * the end.  If a whitespace location can't be found, the
 744      * nearest character is used.
 745      *
 746      * @param axis may be either View.X_AXIS or View.Y_AXIS
 747      * @param p0 the location in the model where the
 748      *  fragment should start it's representation &gt;= 0.
 749      * @param pos the position along the axis that the
 750      *  broken view would occupy &gt;= 0.  This may be useful for
 751      *  things like tab calculations.
 752      * @param len specifies the distance along the axis
 753      *  where a potential break is desired &gt;= 0.
 754      * @return the fragment of the view that represents the
 755      *  given span, if the view can be broken.  If the view
 756      *  doesn't support breaking behavior, the view itself is
 757      *  returned.
 758      * @see View#breakView
 759      */
 760     public View breakView(int axis, int p0, float pos, float len) {
 761         if (axis == View.X_AXIS) {
 762             checkPainter();
 763             int p1 = painter.getBoundedPosition(this, p0, pos, len);
 764             int breakSpot = getBreakSpot(p0, p1);
 765 
 766             if (breakSpot != -1) {
 767                 p1 = breakSpot;
 768             }
 769             // else, no break in the region, return a fragment of the
 770             // bounded region.
 771             if (p0 == getStartOffset() && p1 == getEndOffset()) {
 772                 return this;
 773             }
 774             GlyphView v = (GlyphView) createFragment(p0, p1);
 775             v.x = (int) pos;
 776             return v;
 777         }
 778         return this;
 779     }
 780 
 781     /**
 782      * Returns a location to break at in the passed in region, or
 783      * BreakIterator.DONE if there isn't a good location to break at
 784      * in the specified region.
 785      */
 786     private int getBreakSpot(int p0, int p1) {
 787         if (breakSpots == null) {
 788             // Re-calculate breakpoints for the whole view
 789             int start = getStartOffset();
 790             int end = getEndOffset();
 791             int[] bs = new int[end + 1 - start];
 792             int ix = 0;
 793 
 794             // Breaker should work on the parent element because there may be
 795             // a valid breakpoint at the end edge of the view (space, etc.)
 796             Element parent = getElement().getParentElement();
 797             int pstart = (parent == null ? start : parent.getStartOffset());
 798             int pend = (parent == null ? end : parent.getEndOffset());
 799 
 800             Segment s = getText(pstart, pend);
 801             s.first();
 802             BreakIterator breaker = getBreaker();
 803             breaker.setText(s);
 804 
 805             // Backward search should start from end+1 unless there's NO end+1
 806             int startFrom = end + (pend > end ? 1 : 0);
 807             for (;;) {
 808                 startFrom = breaker.preceding(s.offset + (startFrom - pstart))
 809                           + (pstart - s.offset);
 810                 if (startFrom > start) {
 811                     // The break spot is within the view
 812                     bs[ix++] = startFrom;
 813                 } else {
 814                     break;
 815                 }
 816             }
 817 
 818             SegmentCache.releaseSharedSegment(s);
 819             breakSpots = new int[ix];
 820             System.arraycopy(bs, 0, breakSpots, 0, ix);
 821         }
 822 
 823         int breakSpot = BreakIterator.DONE;
 824         for (int i = 0; i < breakSpots.length; i++) {
 825             int bsp = breakSpots[i];
 826             if (bsp <= p1) {
 827                 if (bsp > p0) {
 828                     breakSpot = bsp;
 829                 }
 830                 break;
 831             }
 832         }
 833         return breakSpot;
 834     }
 835 
 836     /**
 837      * Return break iterator appropriate for the current document.
 838      *
 839      * For non-i18n documents a fast whitespace-based break iterator is used.
 840      */
 841     private BreakIterator getBreaker() {
 842         Document doc = getDocument();
 843         if ((doc != null) && Boolean.TRUE.equals(
 844                     doc.getProperty(AbstractDocument.MultiByteProperty))) {
 845             Container c = getContainer();
 846             Locale locale = (c == null ? Locale.getDefault() : c.getLocale());
 847             return BreakIterator.getLineInstance(locale);
 848         } else {
 849             return new WhitespaceBasedBreakIterator();
 850         }
 851     }
 852 
 853     /**
 854      * Creates a view that represents a portion of the element.
 855      * This is potentially useful during formatting operations
 856      * for taking measurements of fragments of the view.  If
 857      * the view doesn't support fragmenting (the default), it
 858      * should return itself.
 859      * <p>
 860      * This view does support fragmenting.  It is implemented
 861      * to return a nested class that shares state in this view
 862      * representing only a portion of the view.
 863      *
 864      * @param p0 the starting offset &gt;= 0.  This should be a value
 865      *   greater or equal to the element starting offset and
 866      *   less than the element ending offset.
 867      * @param p1 the ending offset &gt; p0.  This should be a value
 868      *   less than or equal to the elements end offset and
 869      *   greater than the elements starting offset.
 870      * @return the view fragment, or itself if the view doesn't
 871      *   support breaking into fragments
 872      * @see LabelView
 873      */
 874     public View createFragment(int p0, int p1) {
 875         checkPainter();
 876         Element elem = getElement();
 877         GlyphView v = (GlyphView) clone();
 878         v.offset = p0 - elem.getStartOffset();
 879         v.length = p1 - p0;
 880         v.painter = painter.getPainter(v, p0, p1);
 881         v.justificationInfo = null;
 882         return v;
 883     }
 884 
 885     /**
 886      * Provides a way to determine the next visually represented model
 887      * location that one might place a caret.  Some views may not be
 888      * visible, they might not be in the same order found in the model, or
 889      * they just might not allow access to some of the locations in the
 890      * model.
 891      * This method enables specifying a position to convert
 892      * within the range of &gt;=0.  If the value is -1, a position
 893      * will be calculated automatically.  If the value &lt; -1,
 894      * the {@code BadLocationException} will be thrown.
 895      *
 896      * @param pos the position to convert
 897      * @param a the allocated region to render into
 898      * @param direction the direction from the current position that can
 899      *  be thought of as the arrow keys typically found on a keyboard.
 900      *  This may be SwingConstants.WEST, SwingConstants.EAST,
 901      *  SwingConstants.NORTH, or SwingConstants.SOUTH.
 902      * @return the location within the model that best represents the next
 903      *  location visual position.
 904      * @exception BadLocationException the given position is not a valid
 905      *                                 position within the document
 906      * @exception IllegalArgumentException for an invalid direction
 907      */
 908     public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
 909                                          int direction,
 910                                          Position.Bias[] biasRet)
 911         throws BadLocationException {
 912 
 913         if (pos < -1 || pos > getDocument().getLength()) {
 914             throw new BadLocationException("invalid position", pos);
 915         }
 916         return painter.getNextVisualPositionFrom(this, pos, b, a, direction, biasRet);
 917     }
 918 
 919     /**
 920      * Gives notification that something was inserted into
 921      * the document in a location that this view is responsible for.
 922      * This is implemented to call preferenceChanged along the
 923      * axis the glyphs are rendered.
 924      *
 925      * @param e the change information from the associated document
 926      * @param a the current allocation of the view
 927      * @param f the factory to use to rebuild if the view has children
 928      * @see View#insertUpdate
 929      */
 930     public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
 931         justificationInfo = null;
 932         breakSpots = null;
 933         minimumSpan = -1;
 934         syncCR();
 935         preferenceChanged(null, true, false);
 936     }
 937 
 938     /**
 939      * Gives notification that something was removed from the document
 940      * in a location that this view is responsible for.
 941      * This is implemented to call preferenceChanged along the
 942      * axis the glyphs are rendered.
 943      *
 944      * @param e the change information from the associated document
 945      * @param a the current allocation of the view
 946      * @param f the factory to use to rebuild if the view has children
 947      * @see View#removeUpdate
 948      */
 949     public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
 950         justificationInfo = null;
 951         breakSpots = null;
 952         minimumSpan = -1;
 953         syncCR();
 954         preferenceChanged(null, true, false);
 955     }
 956 
 957     /**
 958      * Gives notification from the document that attributes were changed
 959      * in a location that this view is responsible for.
 960      * This is implemented to call preferenceChanged along both the
 961      * horizontal and vertical axis.
 962      *
 963      * @param e the change information from the associated document
 964      * @param a the current allocation of the view
 965      * @param f the factory to use to rebuild if the view has children
 966      * @see View#changedUpdate
 967      */
 968     public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
 969         minimumSpan = -1;
 970         syncCR();
 971         preferenceChanged(null, true, true);
 972     }
 973 
 974     // checks if the paragraph is empty and updates impliedCR flag
 975     // accordingly
 976     private void syncCR() {
 977         if (impliedCR) {
 978             Element parent = getElement().getParentElement();
 979             impliedCR = (parent != null && parent.getElementCount() > 1);
 980         }
 981     }
 982 
 983     /** {@inheritDoc} */
 984     @Override
 985     void updateAfterChange() {
 986         // Drop the break spots. They will be re-calculated during
 987         // layout. It is necessary for proper line break calculation.
 988         breakSpots = null;
 989     }
 990 
 991     /**
 992      * Class to hold data needed to justify this GlyphView in a PargraphView.Row
 993      */
 994     static class JustificationInfo {
 995         //justifiable content start
 996         final int start;
 997         //justifiable content end
 998         final int end;
 999         final int leadingSpaces;
1000         final int contentSpaces;
1001         final int trailingSpaces;
1002         final boolean hasTab;
1003         final BitSet spaceMap;
1004         JustificationInfo(int start, int end,
1005                           int leadingSpaces,
1006                           int contentSpaces,
1007                           int trailingSpaces,
1008                           boolean hasTab,
1009                           BitSet spaceMap) {
1010             this.start = start;
1011             this.end = end;
1012             this.leadingSpaces = leadingSpaces;
1013             this.contentSpaces = contentSpaces;
1014             this.trailingSpaces = trailingSpaces;
1015             this.hasTab = hasTab;
1016             this.spaceMap = spaceMap;
1017         }
1018     }
1019 
1020 
1021 
1022     JustificationInfo getJustificationInfo(int rowStartOffset) {
1023         if (justificationInfo != null) {
1024             return justificationInfo;
1025         }
1026         //states for the parsing
1027         final int TRAILING = 0;
1028         final int CONTENT  = 1;
1029         final int SPACES   = 2;
1030         int startOffset = getStartOffset();
1031         int endOffset = getEndOffset();
1032         Segment segment = getText(startOffset, endOffset);
1033         int txtOffset = segment.offset;
1034         int txtEnd = segment.offset + segment.count - 1;
1035         int startContentPosition = txtEnd + 1;
1036         int endContentPosition = txtOffset - 1;
1037         int lastTabPosition = txtOffset - 1;
1038         int trailingSpaces = 0;
1039         int contentSpaces = 0;
1040         int leadingSpaces = 0;
1041         boolean hasTab = false;
1042         BitSet spaceMap = new BitSet(endOffset - startOffset + 1);
1043 
1044         //we parse conent to the right of the rightmost TAB only.
1045         //we are looking for the trailing and leading spaces.
1046         //position after the leading spaces (startContentPosition)
1047         //position before the trailing spaces (endContentPosition)
1048         for (int i = txtEnd, state = TRAILING; i >= txtOffset; i--) {
1049             if (' ' == segment.array[i]) {
1050                 spaceMap.set(i - txtOffset);
1051                 if (state == TRAILING) {
1052                     trailingSpaces++;
1053                 } else if (state == CONTENT) {
1054                     state = SPACES;
1055                     leadingSpaces = 1;
1056                 } else if (state == SPACES) {
1057                     leadingSpaces++;
1058                 }
1059             } else if ('\t' == segment.array[i]) {
1060                 hasTab = true;
1061                 break;
1062             } else {
1063                 if (state == TRAILING) {
1064                     if ('\n' != segment.array[i]
1065                           && '\r' != segment.array[i]) {
1066                         state = CONTENT;
1067                         endContentPosition = i;
1068                     }
1069                 } else if (state == CONTENT) {
1070                     //do nothing
1071                 } else if (state == SPACES) {
1072                     contentSpaces += leadingSpaces;
1073                     leadingSpaces = 0;
1074                 }
1075                 startContentPosition = i;
1076             }
1077         }
1078 
1079         SegmentCache.releaseSharedSegment(segment);
1080 
1081         int startJustifiableContent = -1;
1082         if (startContentPosition < txtEnd) {
1083             startJustifiableContent =
1084                 startContentPosition - txtOffset;
1085         }
1086         int endJustifiableContent = -1;
1087         if (endContentPosition > txtOffset) {
1088             endJustifiableContent =
1089                 endContentPosition - txtOffset;
1090         }
1091         justificationInfo =
1092             new JustificationInfo(startJustifiableContent,
1093                                   endJustifiableContent,
1094                                   leadingSpaces,
1095                                   contentSpaces,
1096                                   trailingSpaces,
1097                                   hasTab,
1098                                   spaceMap);
1099         return justificationInfo;
1100     }
1101 
1102     // --- variables ------------------------------------------------
1103 
1104     /**
1105     * Used by paint() to store highlighted view positions
1106     */
1107     private byte[] selections = null;
1108 
1109     int offset;
1110     int length;
1111     // if it is an implied newline character
1112     boolean impliedCR;
1113     boolean skipWidth;
1114 
1115     /**
1116      * how to expand tabs
1117      */
1118     TabExpander expander;
1119 
1120     /** Cached minimum x-span value  */
1121     private float minimumSpan = -1;
1122 
1123     /** Cached breakpoints within the view  */
1124     private int[] breakSpots = null;
1125 
1126     /**
1127      * location for determining tab expansion against.
1128      */
1129     int x;
1130 
1131     /**
1132      * Glyph rendering functionality.
1133      */
1134     GlyphPainter painter;
1135 
1136     /**
1137      * The prototype painter used by default.
1138      */
1139     static GlyphPainter defaultPainter;
1140 
1141     private JustificationInfo justificationInfo = null;
1142 
1143     /**
1144      * A class to perform rendering of the glyphs.
1145      * This can be implemented to be stateless, or
1146      * to hold some information as a cache to
1147      * facilitate faster rendering and model/view
1148      * translation.  At a minimum, the GlyphPainter
1149      * allows a View implementation to perform its
1150      * duties independant of a particular version
1151      * of JVM and selection of capabilities (i.e.
1152      * shaping for i18n, etc).
1153      *
1154      * @since 1.3
1155      */
1156     public static abstract class GlyphPainter {
1157 
1158         /**
1159          * Determine the span the glyphs given a start location
1160          * (for tab expansion).
1161          * @param v  the {@code GlyphView}
1162          * @param p0 the beginning position
1163          * @param p1 the ending position
1164          * @param e  how to expand the tabs when encountered
1165          * @param x the X coordinate
1166          * @return the span the glyphs given a start location
1167          */
1168         public abstract float getSpan(GlyphView v, int p0, int p1, TabExpander e, float x);
1169 
1170         /**
1171          * Returns of the height.
1172          * @param v  the {@code GlyphView}
1173          * @return of the height
1174          */
1175         public abstract float getHeight(GlyphView v);
1176 
1177         /**
1178          * Returns of the ascent.
1179          * @param v  the {@code GlyphView}
1180          * @return of the ascent
1181          */
1182         public abstract float getAscent(GlyphView v);
1183 
1184         /**
1185          * Returns of the descent.
1186          * @param v  the {@code GlyphView}
1187          * @return of the descent
1188          */
1189         public abstract float getDescent(GlyphView v);
1190 
1191         /**
1192          * Paint the glyphs representing the given range.
1193          * @param v the {@code GlyphView}
1194          * @param g the graphics context
1195          * @param a the current allocation of the view
1196          * @param p0 the beginning position
1197          * @param p1 the ending position
1198          */
1199         public abstract void paint(GlyphView v, Graphics g, Shape a, int p0, int p1);
1200 
1201         /**
1202          * Provides a mapping from the document model coordinate space
1203          * to the coordinate space of the view mapped to it.
1204          * This is shared by the broken views.
1205          *
1206          * @param v     the <code>GlyphView</code> containing the
1207          *              destination coordinate space
1208          * @param pos   the position to convert
1209          * @param bias  either <code>Position.Bias.Forward</code>
1210          *                  or <code>Position.Bias.Backward</code>
1211          * @param a     Bounds of the View
1212          * @return      the bounding box of the given position
1213          * @exception BadLocationException  if the given position does not represent a
1214          *   valid location in the associated document
1215          * @see View#modelToView
1216          */
1217         public abstract Shape modelToView(GlyphView v,
1218                                           int pos, Position.Bias bias,
1219                                           Shape a) throws BadLocationException;
1220 
1221         /**
1222          * Provides a mapping from the view coordinate space to the logical
1223          * coordinate space of the model.
1224          *
1225          * @param v          the <code>GlyphView</code> to provide a mapping for
1226          * @param x          the X coordinate
1227          * @param y          the Y coordinate
1228          * @param a          the allocated region to render into
1229          * @param biasReturn either <code>Position.Bias.Forward</code>
1230          *                   or <code>Position.Bias.Backward</code>
1231          *                   is returned as the zero-th element of this array
1232          * @return the location within the model that best represents the
1233          *         given point of view
1234          * @see View#viewToModel
1235          */
1236         public abstract int viewToModel(GlyphView v,
1237                                         float x, float y, Shape a,
1238                                         Position.Bias[] biasReturn);
1239 
1240         /**
1241          * Determines the model location that represents the
1242          * maximum advance that fits within the given span.
1243          * This could be used to break the given view.  The result
1244          * should be a location just shy of the given advance.  This
1245          * differs from viewToModel which returns the closest
1246          * position which might be proud of the maximum advance.
1247          *
1248          * @param v the view to find the model location to break at.
1249          * @param p0 the location in the model where the
1250          *  fragment should start it's representation &gt;= 0.
1251          * @param x  the graphic location along the axis that the
1252          *  broken view would occupy &gt;= 0.  This may be useful for
1253          *  things like tab calculations.
1254          * @param len specifies the distance into the view
1255          *  where a potential break is desired &gt;= 0.
1256          * @return the maximum model location possible for a break.
1257          * @see View#breakView
1258          */
1259         public abstract int getBoundedPosition(GlyphView v, int p0, float x, float len);
1260 
1261         /**
1262          * Create a painter to use for the given GlyphView.  If
1263          * the painter carries state it can create another painter
1264          * to represent a new GlyphView that is being created.  If
1265          * the painter doesn't hold any significant state, it can
1266          * return itself.  The default behavior is to return itself.
1267          * @param v  the <code>GlyphView</code> to provide a painter for
1268          * @param p0 the starting document offset &gt;= 0
1269          * @param p1 the ending document offset &gt;= p0
1270          * @return a painter to use for the given GlyphView
1271          */
1272         public GlyphPainter getPainter(GlyphView v, int p0, int p1) {
1273             return this;
1274         }
1275 
1276         /**
1277          * Provides a way to determine the next visually represented model
1278          * location that one might place a caret.  Some views may not be
1279          * visible, they might not be in the same order found in the model, or
1280          * they just might not allow access to some of the locations in the
1281          * model.
1282          *
1283          * @param v the view to use
1284          * @param pos the position to convert &gt;= 0
1285          * @param b   either <code>Position.Bias.Forward</code>
1286          *                or <code>Position.Bias.Backward</code>
1287          * @param a the allocated region to render into
1288          * @param direction the direction from the current position that can
1289          *  be thought of as the arrow keys typically found on a keyboard.
1290          *  This may be SwingConstants.WEST, SwingConstants.EAST,
1291          *  SwingConstants.NORTH, or SwingConstants.SOUTH.
1292          * @param biasRet  either <code>Position.Bias.Forward</code>
1293          *                 or <code>Position.Bias.Backward</code>
1294          *                 is returned as the zero-th element of this array
1295          * @return the location within the model that best represents the next
1296          *  location visual position.
1297          * @exception BadLocationException for a bad location within a document model
1298          * @exception IllegalArgumentException for an invalid direction
1299          */
1300         public int getNextVisualPositionFrom(GlyphView v, int pos, Position.Bias b, Shape a,
1301                                              int direction,
1302                                              Position.Bias[] biasRet)
1303             throws BadLocationException {
1304 
1305             int startOffset = v.getStartOffset();
1306             int endOffset = v.getEndOffset();
1307             Segment text;
1308 
1309             switch (direction) {
1310             case View.NORTH:
1311             case View.SOUTH:
1312                 if (pos != -1) {
1313                     // Presumably pos is between startOffset and endOffset,
1314                     // since GlyphView is only one line, we won't contain
1315                     // the position to the nort/south, therefore return -1.
1316                     return -1;
1317                 }
1318                 Container container = v.getContainer();
1319 
1320                 if (container instanceof JTextComponent) {
1321                     Caret c = ((JTextComponent)container).getCaret();
1322                     Point magicPoint;
1323                     magicPoint = (c != null) ? c.getMagicCaretPosition() :null;
1324 
1325                     if (magicPoint == null) {
1326                         biasRet[0] = Position.Bias.Forward;
1327                         return startOffset;
1328                     }
1329                     int value = v.viewToModel(magicPoint.x, 0f, a, biasRet);
1330                     return value;
1331                 }
1332                 break;
1333             case View.EAST:
1334                 if(startOffset == v.getDocument().getLength()) {
1335                     if(pos == -1) {
1336                         biasRet[0] = Position.Bias.Forward;
1337                         return startOffset;
1338                     }
1339                     // End case for bidi text where newline is at beginning
1340                     // of line.
1341                     return -1;
1342                 }
1343                 if(pos == -1) {
1344                     biasRet[0] = Position.Bias.Forward;
1345                     return startOffset;
1346                 }
1347                 if(pos == endOffset) {
1348                     return -1;
1349                 }
1350                 if(++pos == endOffset) {
1351                     // Assumed not used in bidi text, GlyphPainter2 will
1352                     // override as necessary, therefore return -1.
1353                     return -1;
1354                 }
1355                 else {
1356                     biasRet[0] = Position.Bias.Forward;
1357                 }
1358                 return pos;
1359             case View.WEST:
1360                 if(startOffset == v.getDocument().getLength()) {
1361                     if(pos == -1) {
1362                         biasRet[0] = Position.Bias.Forward;
1363                         return startOffset;
1364                     }
1365                     // End case for bidi text where newline is at beginning
1366                     // of line.
1367                     return -1;
1368                 }
1369                 if(pos == -1) {
1370                     // Assumed not used in bidi text, GlyphPainter2 will
1371                     // override as necessary, therefore return -1.
1372                     biasRet[0] = Position.Bias.Forward;
1373                     return endOffset - 1;
1374                 }
1375                 if(pos == startOffset) {
1376                     return -1;
1377                 }
1378                 biasRet[0] = Position.Bias.Forward;
1379                 return (pos - 1);
1380             default:
1381                 throw new IllegalArgumentException("Bad direction: " + direction);
1382             }
1383             return pos;
1384 
1385         }
1386     }
1387 }