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