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