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