1 /*
   2  * Copyright (c) 1997, 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.util.Arrays;
  28 import java.awt.*;
  29 import java.awt.font.TextAttribute;
  30 import javax.swing.event.*;
  31 import javax.swing.SizeRequirements;
  32 
  33 /**
  34  * View of a simple line-wrapping paragraph that supports
  35  * multiple fonts, colors, components, icons, etc.  It is
  36  * basically a vertical box with a margin around it.  The
  37  * contents of the box are a bunch of rows which are special
  38  * horizontal boxes.  This view creates a collection of
  39  * views that represent the child elements of the paragraph
  40  * element.  Each of these views are placed into a row
  41  * directly if they will fit, otherwise the <code>breakView</code>
  42  * method is called to try and carve the view into pieces
  43  * that fit.
  44  *
  45  * @author  Timothy Prinzing
  46  * @author  Scott Violet
  47  * @author  Igor Kushnirskiy
  48  * @see     View
  49  */
  50 public class ParagraphView extends FlowView implements TabExpander {
  51 
  52     /**
  53      * Constructs a <code>ParagraphView</code> for the given element.
  54      *
  55      * @param elem the element that this view is responsible for
  56      */
  57     public ParagraphView(Element elem) {
  58         super(elem, View.Y_AXIS);
  59         setPropertiesFromAttributes();
  60         Document doc = elem.getDocument();
  61         Object i18nFlag = doc.getProperty(AbstractDocument.I18NProperty);
  62         if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) {
  63             try {
  64                 if (i18nStrategy == null) {
  65                     // the classname should probably come from a property file.
  66                     String classname = "javax.swing.text.TextLayoutStrategy";
  67                     ClassLoader loader = getClass().getClassLoader();
  68                     if (loader != null) {
  69                         i18nStrategy = loader.loadClass(classname);
  70                     } else {
  71                         i18nStrategy = Class.forName(classname);
  72                     }
  73                 }
  74                 Object o = i18nStrategy.newInstance();
  75                 if (o instanceof FlowStrategy) {
  76                     strategy = (FlowStrategy) o;
  77                 }
  78             } catch (Throwable e) {
  79                 throw new StateInvariantError("ParagraphView: Can't create i18n strategy: "
  80                                               + e.getMessage());
  81             }
  82         }
  83     }
  84 
  85     /**
  86      * Sets the type of justification.
  87      *
  88      * @param j one of the following values:
  89      * <ul>
  90      * <li><code>StyleConstants.ALIGN_LEFT</code>
  91      * <li><code>StyleConstants.ALIGN_CENTER</code>
  92      * <li><code>StyleConstants.ALIGN_RIGHT</code>
  93      * </ul>
  94      */
  95     protected void setJustification(int j) {
  96         justification = j;
  97     }
  98 
  99     /**
 100      * Sets the line spacing.
 101      *
 102      * @param ls the value is a factor of the line hight
 103      */
 104     protected void setLineSpacing(float ls) {
 105         lineSpacing = ls;
 106     }
 107 
 108     /**
 109      * Sets the indent on the first line.
 110      *
 111      * @param fi the value in points
 112      */
 113     protected void setFirstLineIndent(float fi) {
 114         firstLineIndent = (int) fi;
 115     }
 116 
 117     /**
 118      * Set the cached properties from the attributes.
 119      */
 120     protected void setPropertiesFromAttributes() {
 121         AttributeSet attr = getAttributes();
 122         if (attr != null) {
 123             setParagraphInsets(attr);
 124             Integer a = (Integer)attr.getAttribute(StyleConstants.Alignment);
 125             int alignment;
 126             if (a == null) {
 127                 Document doc = getElement().getDocument();
 128                 Object o = doc.getProperty(TextAttribute.RUN_DIRECTION);
 129                 if ((o != null) && o.equals(TextAttribute.RUN_DIRECTION_RTL)) {
 130                     alignment = StyleConstants.ALIGN_RIGHT;
 131                 } else {
 132                     alignment = StyleConstants.ALIGN_LEFT;
 133                 }
 134             } else {
 135                 alignment = a.intValue();
 136             }
 137             setJustification(alignment);
 138             setLineSpacing(StyleConstants.getLineSpacing(attr));
 139             setFirstLineIndent(StyleConstants.getFirstLineIndent(attr));
 140         }
 141     }
 142 
 143     /**
 144      * Returns the number of views that this view is
 145      * responsible for.
 146      * The child views of the paragraph are rows which
 147      * have been used to arrange pieces of the <code>View</code>s
 148      * that represent the child elements.  This is the number
 149      * of views that have been tiled in two dimensions,
 150      * and should be equivalent to the number of child elements
 151      * to the element this view is responsible for.
 152      *
 153      * @return the number of views that this <code>ParagraphView</code>
 154      *          is responsible for
 155      */
 156     protected int getLayoutViewCount() {
 157         return layoutPool.getViewCount();
 158     }
 159 
 160     /**
 161      * Returns the view at a given <code>index</code>.
 162      * The child views of the paragraph are rows which
 163      * have been used to arrange pieces of the <code>Views</code>
 164      * that represent the child elements.  This methods returns
 165      * the view responsible for the child element index
 166      * (prior to breaking).  These are the Views that were
 167      * produced from a factory (to represent the child
 168      * elements) and used for layout.
 169      *
 170      * @param index the <code>index</code> of the desired view
 171      * @return the view at <code>index</code>
 172      */
 173     protected View getLayoutView(int index) {
 174         return layoutPool.getView(index);
 175     }
 176 
 177     /**
 178      * Returns the next visual position for the cursor, in
 179      * either the east or west direction.
 180      * Overridden from <code>CompositeView</code>.
 181      * @param pos position into the model
 182      * @param b either <code>Position.Bias.Forward</code> or
 183      *          <code>Position.Bias.Backward</code>
 184      * @param a the allocated region to render into
 185      * @param direction either <code>SwingConstants.NORTH</code>
 186      *          or <code>SwingConstants.SOUTH</code>
 187      * @param biasRet an array containing the bias that were checked
 188      *  in this method
 189      * @return the location in the model that represents the
 190      *  next location visual position
 191      */
 192     protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b,
 193                                                       Shape a, int direction,
 194                                                       Position.Bias[] biasRet)
 195                                                 throws BadLocationException {
 196         int vIndex;
 197         if(pos == -1) {
 198             vIndex = (direction == NORTH) ?
 199                      getViewCount() - 1 : 0;
 200         }
 201         else {
 202             if(b == Position.Bias.Backward && pos > 0) {
 203                 vIndex = getViewIndexAtPosition(pos - 1);
 204             }
 205             else {
 206                 vIndex = getViewIndexAtPosition(pos);
 207             }
 208             if(direction == NORTH) {
 209                 if(vIndex == 0) {
 210                     return -1;
 211                 }
 212                 vIndex--;
 213             }
 214             else if(++vIndex >= getViewCount()) {
 215                 return -1;
 216             }
 217         }
 218         // vIndex gives index of row to look in.
 219         JTextComponent text = (JTextComponent)getContainer();
 220         Caret c = text.getCaret();
 221         Point magicPoint;
 222         magicPoint = (c != null) ? c.getMagicCaretPosition() : null;
 223         int x;
 224         if(magicPoint == null) {
 225             Shape posBounds;
 226             try {
 227                 posBounds = text.getUI().modelToView(text, pos, b);
 228             } catch (BadLocationException exc) {
 229                 posBounds = null;
 230             }
 231             if(posBounds == null) {
 232                 x = 0;
 233             }
 234             else {
 235                 x = posBounds.getBounds().x;
 236             }
 237         }
 238         else {
 239             x = magicPoint.x;
 240         }
 241         return getClosestPositionTo(pos, b, a, direction, biasRet, vIndex, x);
 242     }
 243 
 244     /**
 245      * Returns the closest model position to <code>x</code>.
 246      * <code>rowIndex</code> gives the index of the view that corresponds
 247      * that should be looked in.
 248      * @param pos  position into the model
 249      * @param a the allocated region to render into
 250      * @param direction one of the following values:
 251      * <ul>
 252      * <li><code>SwingConstants.NORTH</code>
 253      * <li><code>SwingConstants.SOUTH</code>
 254      * </ul>
 255      * @param biasRet an array containing the bias that were checked
 256      *  in this method
 257      * @param rowIndex the index of the view
 258      * @param x the x coordinate of interest
 259      * @return the closest model position to <code>x</code>
 260      */
 261     // NOTE: This will not properly work if ParagraphView contains
 262     // other ParagraphViews. It won't raise, but this does not message
 263     // the children views with getNextVisualPositionFrom.
 264     protected int getClosestPositionTo(int pos, Position.Bias b, Shape a,
 265                                        int direction, Position.Bias[] biasRet,
 266                                        int rowIndex, int x)
 267               throws BadLocationException {
 268         JTextComponent text = (JTextComponent)getContainer();
 269         Document doc = getDocument();
 270         View row = getView(rowIndex);
 271         int lastPos = -1;
 272         // This could be made better to check backward positions too.
 273         biasRet[0] = Position.Bias.Forward;
 274         for(int vc = 0, numViews = row.getViewCount(); vc < numViews; vc++) {
 275             View v = row.getView(vc);
 276             int start = v.getStartOffset();
 277             boolean ltr = AbstractDocument.isLeftToRight(doc, start, start + 1);
 278             if(ltr) {
 279                 lastPos = start;
 280                 for(int end = v.getEndOffset(); lastPos < end; lastPos++) {
 281                     float xx = text.modelToView(lastPos).getBounds().x;
 282                     if(xx >= x) {
 283                         while (++lastPos < end &&
 284                                text.modelToView(lastPos).getBounds().x == xx) {
 285                         }
 286                         return --lastPos;
 287                     }
 288                 }
 289                 lastPos--;
 290             }
 291             else {
 292                 for(lastPos = v.getEndOffset() - 1; lastPos >= start;
 293                     lastPos--) {
 294                     float xx = text.modelToView(lastPos).getBounds().x;
 295                     if(xx >= x) {
 296                         while (--lastPos >= start &&
 297                                text.modelToView(lastPos).getBounds().x == xx) {
 298                         }
 299                         return ++lastPos;
 300                     }
 301                 }
 302                 lastPos++;
 303             }
 304         }
 305         if(lastPos == -1) {
 306             return getStartOffset();
 307         }
 308         return lastPos;
 309     }
 310 
 311     /**
 312      * Determines in which direction the next view lays.
 313      * Consider the <code>View</code> at index n.
 314      * Typically the <code>View</code>s are layed out
 315      * from left to right, so that the <code>View</code>
 316      * to the EAST will be at index n + 1, and the
 317      * <code>View</code> to the WEST will be at index n - 1.
 318      * In certain situations, such as with bidirectional text,
 319      * it is possible that the <code>View</code> to EAST is not
 320      * at index n + 1, but rather at index n - 1,
 321      * or that the <code>View</code> to the WEST is not at
 322      * index n - 1, but index n + 1.  In this case this method
 323      * would return true, indicating the <code>View</code>s are
 324      * layed out in descending order.
 325      * <p>
 326      * This will return true if the text is layed out right
 327      * to left at position, otherwise false.
 328      *
 329      * @param position position into the model
 330      * @param bias either <code>Position.Bias.Forward</code> or
 331      *          <code>Position.Bias.Backward</code>
 332      * @return true if the text is layed out right to left at
 333      *         position, otherwise false.
 334      */
 335     protected boolean flipEastAndWestAtEnds(int position,
 336                                             Position.Bias bias) {
 337         Document doc = getDocument();
 338         position = getStartOffset();
 339         return !AbstractDocument.isLeftToRight(doc, position, position + 1);
 340     }
 341 
 342     // --- FlowView methods ---------------------------------------------
 343 
 344     /**
 345      * Fetches the constraining span to flow against for
 346      * the given child index.
 347      * @param index the index of the view being queried
 348      * @return the constraining span for the given view at
 349      *  <code>index</code>
 350      * @since 1.3
 351      */
 352     public int getFlowSpan(int index) {
 353         View child = getView(index);
 354         int adjust = 0;
 355         if (child instanceof Row) {
 356             Row row = (Row) child;
 357             adjust = row.getLeftInset() + row.getRightInset();
 358         }
 359         return (layoutSpan == Integer.MAX_VALUE) ? layoutSpan
 360                                                  : (layoutSpan - adjust);
 361     }
 362 
 363     /**
 364      * Fetches the location along the flow axis that the
 365      * flow span will start at.
 366      * @param index the index of the view being queried
 367      * @return the location for the given view at
 368      *  <code>index</code>
 369      * @since 1.3
 370      */
 371     public int getFlowStart(int index) {
 372         View child = getView(index);
 373         int adjust = 0;
 374         if (child instanceof Row) {
 375             Row row = (Row) child;
 376             adjust = row.getLeftInset();
 377         }
 378         return tabBase + adjust;
 379     }
 380 
 381     /**
 382      * Create a <code>View</code> that should be used to hold a
 383      * a row's worth of children in a flow.
 384      * @return the new <code>View</code>
 385      * @since 1.3
 386      */
 387     protected View createRow() {
 388         return new Row(getElement());
 389     }
 390 
 391     // --- TabExpander methods ------------------------------------------
 392 
 393     /**
 394      * Returns the next tab stop position given a reference position.
 395      * This view implements the tab coordinate system, and calls
 396      * <code>getTabbedSpan</code> on the logical children in the process
 397      * of layout to determine the desired span of the children.  The
 398      * logical children can delegate their tab expansion upward to
 399      * the paragraph which knows how to expand tabs.
 400      * <code>LabelView</code> is an example of a view that delegates
 401      * its tab expansion needs upward to the paragraph.
 402      * <p>
 403      * This is implemented to try and locate a <code>TabSet</code>
 404      * in the paragraph element's attribute set.  If one can be
 405      * found, its settings will be used, otherwise a default expansion
 406      * will be provided.  The base location for for tab expansion
 407      * is the left inset from the paragraphs most recent allocation
 408      * (which is what the layout of the children is based upon).
 409      *
 410      * @param x the X reference position
 411      * @param tabOffset the position within the text stream
 412      *   that the tab occurred at &gt;= 0
 413      * @return the trailing end of the tab expansion &gt;= 0
 414      * @see TabSet
 415      * @see TabStop
 416      * @see LabelView
 417      */
 418     public float nextTabStop(float x, int tabOffset) {
 419         // If the text isn't left justified, offset by 10 pixels!
 420         if(justification != StyleConstants.ALIGN_LEFT)
 421             return x + 10.0f;
 422         x -= tabBase;
 423         TabSet tabs = getTabSet();
 424         if(tabs == null) {
 425             // a tab every 72 pixels.
 426             return (float)(tabBase + (((int)x / 72 + 1) * 72));
 427         }
 428         TabStop tab = tabs.getTabAfter(x + .01f);
 429         if(tab == null) {
 430             // no tab, do a default of 5 pixels.
 431             // Should this cause a wrapping of the line?
 432             return tabBase + x + 5.0f;
 433         }
 434         int alignment = tab.getAlignment();
 435         int offset;
 436         switch(alignment) {
 437         default:
 438         case TabStop.ALIGN_LEFT:
 439             // Simple case, left tab.
 440             return tabBase + tab.getPosition();
 441         case TabStop.ALIGN_BAR:
 442             // PENDING: what does this mean?
 443             return tabBase + tab.getPosition();
 444         case TabStop.ALIGN_RIGHT:
 445         case TabStop.ALIGN_CENTER:
 446             offset = findOffsetToCharactersInString(tabChars,
 447                                                     tabOffset + 1);
 448             break;
 449         case TabStop.ALIGN_DECIMAL:
 450             offset = findOffsetToCharactersInString(tabDecimalChars,
 451                                                     tabOffset + 1);
 452             break;
 453         }
 454         if (offset == -1) {
 455             offset = getEndOffset();
 456         }
 457         float charsSize = getPartialSize(tabOffset + 1, offset);
 458         switch(alignment) {
 459         case TabStop.ALIGN_RIGHT:
 460         case TabStop.ALIGN_DECIMAL:
 461             // right and decimal are treated the same way, the new
 462             // position will be the location of the tab less the
 463             // partialSize.
 464             return tabBase + Math.max(x, tab.getPosition() - charsSize);
 465         case TabStop.ALIGN_CENTER:
 466             // Similar to right, but half the partialSize.
 467             return tabBase + Math.max(x, tab.getPosition() - charsSize / 2.0f);
 468         }
 469         // will never get here!
 470         return x;
 471     }
 472 
 473     /**
 474      * Gets the <code>Tabset</code> to be used in calculating tabs.
 475      *
 476      * @return the <code>TabSet</code>
 477      */
 478     protected TabSet getTabSet() {
 479         return StyleConstants.getTabSet(getElement().getAttributes());
 480     }
 481 
 482     /**
 483      * Returns the size used by the views between
 484      * <code>startOffset</code> and <code>endOffset</code>.
 485      * This uses <code>getPartialView</code> to calculate the
 486      * size if the child view implements the
 487      * <code>TabableView</code> interface. If a
 488      * size is needed and a <code>View</code> does not implement
 489      * the <code>TabableView</code> interface,
 490      * the <code>preferredSpan</code> will be used.
 491      *
 492      * @param startOffset the starting document offset &gt;= 0
 493      * @param endOffset the ending document offset &gt;= startOffset
 494      * @return the size &gt;= 0
 495      */
 496     protected float getPartialSize(int startOffset, int endOffset) {
 497         float size = 0.0f;
 498         int viewIndex;
 499         int numViews = getViewCount();
 500         View view;
 501         int viewEnd;
 502         int tempEnd;
 503 
 504         // Have to search layoutPool!
 505         // PENDING: when ParagraphView supports breaking location
 506         // into layoutPool will have to change!
 507         viewIndex = getElement().getElementIndex(startOffset);
 508         numViews = layoutPool.getViewCount();
 509         while(startOffset < endOffset && viewIndex < numViews) {
 510             view = layoutPool.getView(viewIndex++);
 511             viewEnd = view.getEndOffset();
 512             tempEnd = Math.min(endOffset, viewEnd);
 513             if(view instanceof TabableView)
 514                 size += ((TabableView)view).getPartialSpan(startOffset, tempEnd);
 515             else if(startOffset == view.getStartOffset() &&
 516                     tempEnd == view.getEndOffset())
 517                 size += view.getPreferredSpan(View.X_AXIS);
 518             else
 519                 // PENDING: should we handle this better?
 520                 return 0.0f;
 521             startOffset = viewEnd;
 522         }
 523         return size;
 524     }
 525 
 526     /**
 527      * Finds the next character in the document with a character in
 528      * <code>string</code>, starting at offset <code>start</code>. If
 529      * there are no characters found, -1 will be returned.
 530      *
 531      * @param string the string of characters
 532      * @param start where to start in the model &gt;= 0
 533      * @return the document offset, or -1 if no characters found
 534      */
 535     protected int findOffsetToCharactersInString(char[] string,
 536                                                  int start) {
 537         int stringLength = string.length;
 538         int end = getEndOffset();
 539         Segment seg = new Segment();
 540         try {
 541             getDocument().getText(start, end - start, seg);
 542         } catch (BadLocationException ble) {
 543             return -1;
 544         }
 545         for(int counter = seg.offset, maxCounter = seg.offset + seg.count;
 546             counter < maxCounter; counter++) {
 547             char currentChar = seg.array[counter];
 548             for(int subCounter = 0; subCounter < stringLength;
 549                 subCounter++) {
 550                 if(currentChar == string[subCounter])
 551                     return counter - seg.offset + start;
 552             }
 553         }
 554         // No match.
 555         return -1;
 556     }
 557 
 558     /**
 559      * Returns where the tabs are calculated from.
 560      * @return where tabs are calculated from
 561      */
 562     protected float getTabBase() {
 563         return (float)tabBase;
 564     }
 565 
 566     // ---- View methods ----------------------------------------------------
 567 
 568     /**
 569      * Renders using the given rendering surface and area on that
 570      * surface.  This is implemented to delegate to the superclass
 571      * after stashing the base coordinate for tab calculations.
 572      *
 573      * @param g the rendering surface to use
 574      * @param a the allocated region to render into
 575      * @see View#paint
 576      */
 577     public void paint(Graphics g, Shape a) {
 578         Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
 579         tabBase = alloc.x + getLeftInset();
 580         super.paint(g, a);
 581 
 582         // line with the negative firstLineIndent value needs
 583         // special handling
 584         if (firstLineIndent < 0) {
 585             Shape sh = getChildAllocation(0, a);
 586             if ((sh != null) &&  sh.intersects(alloc)) {
 587                 int x = alloc.x + getLeftInset() + firstLineIndent;
 588                 int y = alloc.y + getTopInset();
 589 
 590                 Rectangle clip = g.getClipBounds();
 591                 tempRect.x = x + getOffset(X_AXIS, 0);
 592                 tempRect.y = y + getOffset(Y_AXIS, 0);
 593                 tempRect.width = getSpan(X_AXIS, 0) - firstLineIndent;
 594                 tempRect.height = getSpan(Y_AXIS, 0);
 595                 if (tempRect.intersects(clip)) {
 596                     tempRect.x = tempRect.x - firstLineIndent;
 597                     paintChild(g, tempRect, 0);
 598                 }
 599             }
 600         }
 601     }
 602 
 603     /**
 604      * Determines the desired alignment for this view along an
 605      * axis.  This is implemented to give the alignment to the
 606      * center of the first row along the y axis, and the default
 607      * along the x axis.
 608      *
 609      * @param axis may be either <code>View.X_AXIS</code> or
 610      *   <code>View.Y_AXIS</code>
 611      * @return the desired alignment.  This should be a value
 612      *   between 0.0 and 1.0 inclusive, where 0 indicates alignment at the
 613      *   origin and 1.0 indicates alignment to the full span
 614      *   away from the origin.  An alignment of 0.5 would be the
 615      *   center of the view.
 616      */
 617     public float getAlignment(int axis) {
 618         switch (axis) {
 619         case Y_AXIS:
 620             float a = 0.5f;
 621             if (getViewCount() != 0) {
 622                 int paragraphSpan = (int) getPreferredSpan(View.Y_AXIS);
 623                 View v = getView(0);
 624                 int rowSpan = (int) v.getPreferredSpan(View.Y_AXIS);
 625                 a = (paragraphSpan != 0) ? ((float)(rowSpan / 2)) / paragraphSpan : 0;
 626             }
 627             return a;
 628         case X_AXIS:
 629             return 0.5f;
 630         default:
 631             throw new IllegalArgumentException("Invalid axis: " + axis);
 632         }
 633     }
 634 
 635     /**
 636      * Breaks this view on the given axis at the given length.
 637      * <p>
 638      * <code>ParagraphView</code> instances are breakable
 639      * along the <code>Y_AXIS</code> only, and only if
 640      * <code>len</code> is after the first line.
 641      *
 642      * @param axis may be either <code>View.X_AXIS</code>
 643      *  or <code>View.Y_AXIS</code>
 644      * @param len specifies where a potential break is desired
 645      *  along the given axis &gt;= 0
 646      * @param a the current allocation of the view
 647      * @return the fragment of the view that represents the
 648      *  given span, if the view can be broken; if the view
 649      *  doesn't support breaking behavior, the view itself is
 650      *  returned
 651      * @see View#breakView
 652      */
 653     public View breakView(int axis, float len, Shape a) {
 654         if(axis == View.Y_AXIS) {
 655             if(a != null) {
 656                 Rectangle alloc = a.getBounds();
 657                 setSize(alloc.width, alloc.height);
 658             }
 659             // Determine what row to break on.
 660 
 661             // PENDING(prinz) add break support
 662             return this;
 663         }
 664         return this;
 665     }
 666 
 667     /**
 668      * Gets the break weight for a given location.
 669      * <p>
 670      * <code>ParagraphView</code> instances are breakable
 671      * along the <code>Y_AXIS</code> only, and only if
 672      * <code>len</code> is after the first row.  If the length
 673      * is less than one row, a value of <code>BadBreakWeight</code>
 674      * is returned.
 675      *
 676      * @param axis may be either <code>View.X_AXIS</code>
 677      *  or <code>View.Y_AXIS</code>
 678      * @param len specifies where a potential break is desired &gt;= 0
 679      * @return a value indicating the attractiveness of breaking here;
 680      *  either <code>GoodBreakWeight</code> or <code>BadBreakWeight</code>
 681      * @see View#getBreakWeight
 682      */
 683     public int getBreakWeight(int axis, float len) {
 684         if(axis == View.Y_AXIS) {
 685             // PENDING(prinz) make this return a reasonable value
 686             // when paragraph breaking support is re-implemented.
 687             // If less than one row, bad weight value should be
 688             // returned.
 689             //return GoodBreakWeight;
 690             return BadBreakWeight;
 691         }
 692         return BadBreakWeight;
 693     }
 694 
 695     /**
 696      * Calculate the needs for the paragraph along the minor axis.
 697      *
 698      * <p>This uses size requirements of the superclass, modified to take into
 699      * account the non-breakable areas at the adjacent views edges.  The minimal
 700      * size requirements for such views should be no less than the sum of all
 701      * adjacent fragments.</p>
 702      *
 703      * <p>If the {@code axis} parameter is neither {@code View.X_AXIS} nor
 704      * {@code View.Y_AXIS}, {@link IllegalArgumentException} is thrown.  If the
 705      * {@code r} parameter is {@code null,} a new {@code SizeRequirements}
 706      * object is created, otherwise the supplied {@code SizeRequirements}
 707      * object is returned.</p>
 708      *
 709      * @param axis  the minor axis
 710      * @param r     the input {@code SizeRequirements} object
 711      * @return      the new or adjusted {@code SizeRequirements} object
 712      * @throws IllegalArgumentException  if the {@code axis} parameter is invalid
 713      */
 714     @Override
 715     protected SizeRequirements calculateMinorAxisRequirements(int axis,
 716                                                         SizeRequirements r) {
 717         r = super.calculateMinorAxisRequirements(axis, r);
 718 
 719         float min = 0;
 720         float glue = 0;
 721         int n = getLayoutViewCount();
 722         for (int i = 0; i < n; i++) {
 723             View v = getLayoutView(i);
 724             float span = v.getMinimumSpan(axis);
 725             if (v.getBreakWeight(axis, 0, v.getMaximumSpan(axis)) > View.BadBreakWeight) {
 726                 // find the longest non-breakable fragments at the view edges
 727                 int p0 = v.getStartOffset();
 728                 int p1 = v.getEndOffset();
 729                 float start = findEdgeSpan(v, axis, p0, p0, p1);
 730                 float end = findEdgeSpan(v, axis, p1, p0, p1);
 731                 glue += start;
 732                 min = Math.max(min, Math.max(span, glue));
 733                 glue = end;
 734             } else {
 735                 // non-breakable view
 736                 glue += span;
 737                 min = Math.max(min, glue);
 738             }
 739         }
 740         r.minimum = Math.max(r.minimum, (int) min);
 741         r.preferred = Math.max(r.minimum, r.preferred);
 742         r.maximum = Math.max(r.preferred, r.maximum);
 743 
 744         return r;
 745     }
 746 
 747     /**
 748      * Binary search for the longest non-breakable fragment at the view edge.
 749      */
 750     private float findEdgeSpan(View v, int axis, int fp, int p0, int p1) {
 751         int len = p1 - p0;
 752         if (len <= 1) {
 753             // further fragmentation is not possible
 754             return v.getMinimumSpan(axis);
 755         } else {
 756             int mid = p0 + len / 2;
 757             boolean startEdge = mid > fp;
 758             // initial view is breakable hence must support fragmentation
 759             View f = startEdge ?
 760                 v.createFragment(fp, mid) : v.createFragment(mid, fp);
 761             boolean breakable = f.getBreakWeight(
 762                     axis, 0, f.getMaximumSpan(axis)) > View.BadBreakWeight;
 763             if (breakable == startEdge) {
 764                 p1 = mid;
 765             } else {
 766                 p0 = mid;
 767             }
 768             return findEdgeSpan(f, axis, fp, p0, p1);
 769         }
 770     }
 771 
 772     /**
 773      * Gives notification from the document that attributes were changed
 774      * in a location that this view is responsible for.
 775      *
 776      * @param changes the change information from the
 777      *  associated document
 778      * @param a the current allocation of the view
 779      * @param f the factory to use to rebuild if the view has children
 780      * @see View#changedUpdate
 781      */
 782     public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
 783         // update any property settings stored, and layout should be
 784         // recomputed
 785         setPropertiesFromAttributes();
 786         layoutChanged(X_AXIS);
 787         layoutChanged(Y_AXIS);
 788         super.changedUpdate(changes, a, f);
 789     }
 790 
 791 
 792     // --- variables -----------------------------------------------
 793 
 794     private int justification;
 795     private float lineSpacing;
 796     /** Indentation for the first line, from the left inset. */
 797     protected int firstLineIndent = 0;
 798 
 799     /**
 800      * Used by the TabExpander functionality to determine
 801      * where to base the tab calculations.  This is basically
 802      * the location of the left side of the paragraph.
 803      */
 804     private int tabBase;
 805 
 806     /**
 807      * Used to create an i18n-based layout strategy
 808      */
 809     static Class<?> i18nStrategy;
 810 
 811     /** Used for searching for a tab. */
 812     static char[] tabChars;
 813     /** Used for searching for a tab or decimal character. */
 814     static char[] tabDecimalChars;
 815 
 816     static {
 817         tabChars = new char[1];
 818         tabChars[0] = '\t';
 819         tabDecimalChars = new char[2];
 820         tabDecimalChars[0] = '\t';
 821         tabDecimalChars[1] = '.';
 822     }
 823 
 824     /**
 825      * Internally created view that has the purpose of holding
 826      * the views that represent the children of the paragraph
 827      * that have been arranged in rows.
 828      */
 829     class Row extends BoxView {
 830 
 831         Row(Element elem) {
 832             super(elem, View.X_AXIS);
 833         }
 834 
 835         /**
 836          * This is reimplemented to do nothing since the
 837          * paragraph fills in the row with its needed
 838          * children.
 839          */
 840         protected void loadChildren(ViewFactory f) {
 841         }
 842 
 843         /**
 844          * Fetches the attributes to use when rendering.  This view
 845          * isn't directly responsible for an element so it returns
 846          * the outer classes attributes.
 847          */
 848         public AttributeSet getAttributes() {
 849             View p = getParent();
 850             return (p != null) ? p.getAttributes() : null;
 851         }
 852 
 853         public float getAlignment(int axis) {
 854             if (axis == View.X_AXIS) {
 855                 switch (justification) {
 856                 case StyleConstants.ALIGN_LEFT:
 857                     return 0;
 858                 case StyleConstants.ALIGN_RIGHT:
 859                     return 1;
 860                 case StyleConstants.ALIGN_CENTER:
 861                     return 0.5f;
 862                 case StyleConstants.ALIGN_JUSTIFIED:
 863                     float rv = 0.5f;
 864                     //if we can justifiy the content always align to
 865                     //the left.
 866                     if (isJustifiableDocument()) {
 867                         rv = 0f;
 868                     }
 869                     return rv;
 870                 }
 871             }
 872             return super.getAlignment(axis);
 873         }
 874 
 875         /**
 876          * Provides a mapping from the document model coordinate space
 877          * to the coordinate space of the view mapped to it.  This is
 878          * implemented to let the superclass find the position along
 879          * the major axis and the allocation of the row is used
 880          * along the minor axis, so that even though the children
 881          * are different heights they all get the same caret height.
 882          *
 883          * @param pos the position to convert
 884          * @param a the allocated region to render into
 885          * @return the bounding box of the given position
 886          * @exception BadLocationException  if the given position does not represent a
 887          *   valid location in the associated document
 888          * @see View#modelToView
 889          */
 890         public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
 891             Rectangle r = a.getBounds();
 892             View v = getViewAtPosition(pos, r);
 893             if ((v != null) && (!v.getElement().isLeaf())) {
 894                 // Don't adjust the height if the view represents a branch.
 895                 return super.modelToView(pos, a, b);
 896             }
 897             r = a.getBounds();
 898             int height = r.height;
 899             int y = r.y;
 900             Shape loc = super.modelToView(pos, a, b);
 901             r = loc.getBounds();
 902             r.height = height;
 903             r.y = y;
 904             return r;
 905         }
 906 
 907         /**
 908          * Range represented by a row in the paragraph is only
 909          * a subset of the total range of the paragraph element.
 910          * @see View#getRange
 911          */
 912         public int getStartOffset() {
 913             int offs = Integer.MAX_VALUE;
 914             int n = getViewCount();
 915             for (int i = 0; i < n; i++) {
 916                 View v = getView(i);
 917                 offs = Math.min(offs, v.getStartOffset());
 918             }
 919             return offs;
 920         }
 921 
 922         public int getEndOffset() {
 923             int offs = 0;
 924             int n = getViewCount();
 925             for (int i = 0; i < n; i++) {
 926                 View v = getView(i);
 927                 offs = Math.max(offs, v.getEndOffset());
 928             }
 929             return offs;
 930         }
 931 
 932         /**
 933          * Perform layout for the minor axis of the box (i.e. the
 934          * axis orthogonal to the axis that it represents).  The results
 935          * of the layout should be placed in the given arrays which represent
 936          * the allocations to the children along the minor axis.
 937          * <p>
 938          * This is implemented to do a baseline layout of the children
 939          * by calling BoxView.baselineLayout.
 940          *
 941          * @param targetSpan the total span given to the view, which
 942          *  would be used to layout the children.
 943          * @param axis the axis being layed out.
 944          * @param offsets the offsets from the origin of the view for
 945          *  each of the child views.  This is a return value and is
 946          *  filled in by the implementation of this method.
 947          * @param spans the span of each child view.  This is a return
 948          *  value and is filled in by the implementation of this method.
 949          * @return the offset and span for each child view in the
 950          *  offsets and spans parameters
 951          */
 952         protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
 953             baselineLayout(targetSpan, axis, offsets, spans);
 954         }
 955 
 956         protected SizeRequirements calculateMinorAxisRequirements(int axis,
 957                                                                   SizeRequirements r) {
 958             return baselineRequirements(axis, r);
 959         }
 960 
 961 
 962         private boolean isLastRow() {
 963             View parent;
 964             return ((parent = getParent()) == null
 965                     || this == parent.getView(parent.getViewCount() - 1));
 966         }
 967 
 968         private boolean isBrokenRow() {
 969             boolean rv = false;
 970             int viewsCount = getViewCount();
 971             if (viewsCount > 0) {
 972                 View lastView = getView(viewsCount - 1);
 973                 if (lastView.getBreakWeight(X_AXIS, 0, 0) >=
 974                       ForcedBreakWeight) {
 975                     rv = true;
 976                 }
 977             }
 978             return rv;
 979         }
 980 
 981         private boolean isJustifiableDocument() {
 982             return (! Boolean.TRUE.equals(getDocument().getProperty(
 983                           AbstractDocument.I18NProperty)));
 984         }
 985 
 986         /**
 987          * Whether we need to justify this {@code Row}.
 988          * At this time (jdk1.6) we support justification on for non
 989          * 18n text.
 990          *
 991          * @return {@code true} if this {@code Row} should be justified.
 992          */
 993         private boolean isJustifyEnabled() {
 994             boolean ret = (justification == StyleConstants.ALIGN_JUSTIFIED);
 995 
 996             //no justification for i18n documents
 997             ret = ret && isJustifiableDocument();
 998 
 999             //no justification for the last row
1000             ret = ret && ! isLastRow();
1001 
1002             //no justification for the broken rows
1003             ret = ret && ! isBrokenRow();
1004 
1005             return ret;
1006         }
1007 
1008 
1009         //Calls super method after setting spaceAddon to 0.
1010         //Justification should not affect MajorAxisRequirements
1011         @Override
1012         protected SizeRequirements calculateMajorAxisRequirements(int axis,
1013                 SizeRequirements r) {
1014             int oldJustficationData[] = justificationData;
1015             justificationData = null;
1016             SizeRequirements ret = super.calculateMajorAxisRequirements(axis, r);
1017             if (isJustifyEnabled()) {
1018                 justificationData = oldJustficationData;
1019             }
1020             return ret;
1021         }
1022 
1023         @Override
1024         protected void layoutMajorAxis(int targetSpan, int axis,
1025                                        int[] offsets, int[] spans) {
1026             int oldJustficationData[] = justificationData;
1027             justificationData = null;
1028             super.layoutMajorAxis(targetSpan, axis, offsets, spans);
1029             if (! isJustifyEnabled()) {
1030                 return;
1031             }
1032 
1033             int currentSpan = 0;
1034             for (int span : spans) {
1035                 currentSpan += span;
1036             }
1037             if (currentSpan == targetSpan) {
1038                 //no need to justify
1039                 return;
1040             }
1041 
1042             // we justify text by enlarging spaces by the {@code spaceAddon}.
1043             // justification is started to the right of the rightmost TAB.
1044             // leading and trailing spaces are not extendable.
1045             //
1046             // GlyphPainter1 uses
1047             // justificationData
1048             // for all painting and measurement.
1049 
1050             int extendableSpaces = 0;
1051             int startJustifiableContent = -1;
1052             int endJustifiableContent = -1;
1053             int lastLeadingSpaces = 0;
1054 
1055             int rowStartOffset = getStartOffset();
1056             int rowEndOffset = getEndOffset();
1057             int spaceMap[] = new int[rowEndOffset - rowStartOffset];
1058             Arrays.fill(spaceMap, 0);
1059             for (int i = getViewCount() - 1; i >= 0 ; i--) {
1060                 View view = getView(i);
1061                 if (view instanceof GlyphView) {
1062                     GlyphView.JustificationInfo justificationInfo =
1063                         ((GlyphView) view).getJustificationInfo(rowStartOffset);
1064                     final int viewStartOffset = view.getStartOffset();
1065                     final int offset = viewStartOffset - rowStartOffset;
1066                     for (int j = 0; j < justificationInfo.spaceMap.length(); j++) {
1067                         if (justificationInfo.spaceMap.get(j)) {
1068                             spaceMap[j + offset] = 1;
1069                         }
1070                     }
1071                     if (startJustifiableContent > 0) {
1072                         if (justificationInfo.end >= 0) {
1073                             extendableSpaces += justificationInfo.trailingSpaces;
1074                         } else {
1075                             lastLeadingSpaces += justificationInfo.trailingSpaces;
1076                         }
1077                     }
1078                     if (justificationInfo.start >= 0) {
1079                         startJustifiableContent =
1080                             justificationInfo.start + viewStartOffset;
1081                         extendableSpaces += lastLeadingSpaces;
1082                     }
1083                     if (justificationInfo.end >= 0
1084                           && endJustifiableContent < 0) {
1085                         endJustifiableContent =
1086                             justificationInfo.end + viewStartOffset;
1087                     }
1088                     extendableSpaces += justificationInfo.contentSpaces;
1089                     lastLeadingSpaces = justificationInfo.leadingSpaces;
1090                     if (justificationInfo.hasTab) {
1091                         break;
1092                     }
1093                 }
1094             }
1095             if (extendableSpaces <= 0) {
1096                 //there is nothing we can do to justify
1097                 return;
1098             }
1099             int adjustment = (targetSpan - currentSpan);
1100             int spaceAddon = (extendableSpaces > 0)
1101                 ?  adjustment / extendableSpaces
1102                 : 0;
1103             int spaceAddonLeftoverEnd = -1;
1104             for (int i = startJustifiableContent - rowStartOffset,
1105                      leftover = adjustment - spaceAddon * extendableSpaces;
1106                      leftover > 0;
1107                      leftover -= spaceMap[i],
1108                      i++) {
1109                 spaceAddonLeftoverEnd = i;
1110             }
1111             if (spaceAddon > 0 || spaceAddonLeftoverEnd >= 0) {
1112                 justificationData = (oldJustficationData != null)
1113                     ? oldJustficationData
1114                     : new int[END_JUSTIFIABLE + 1];
1115                 justificationData[SPACE_ADDON] = spaceAddon;
1116                 justificationData[SPACE_ADDON_LEFTOVER_END] =
1117                     spaceAddonLeftoverEnd;
1118                 justificationData[START_JUSTIFIABLE] =
1119                     startJustifiableContent - rowStartOffset;
1120                 justificationData[END_JUSTIFIABLE] =
1121                     endJustifiableContent - rowStartOffset;
1122                 super.layoutMajorAxis(targetSpan, axis, offsets, spans);
1123             }
1124         }
1125 
1126         //for justified row we assume the maximum horizontal span
1127         //is MAX_VALUE.
1128         @Override
1129         public float getMaximumSpan(int axis) {
1130             float ret;
1131             if (View.X_AXIS == axis
1132                   && isJustifyEnabled()) {
1133                 ret = Float.MAX_VALUE;
1134             } else {
1135               ret = super.getMaximumSpan(axis);
1136             }
1137             return ret;
1138         }
1139 
1140         /**
1141          * Fetches the child view index representing the given position in
1142          * the model.
1143          *
1144          * @param pos the position &gt;= 0
1145          * @return  index of the view representing the given position, or
1146          *   -1 if no view represents that position
1147          */
1148         protected int getViewIndexAtPosition(int pos) {
1149             // This is expensive, but are views are not necessarily layed
1150             // out in model order.
1151             if(pos < getStartOffset() || pos >= getEndOffset())
1152                 return -1;
1153             for(int counter = getViewCount() - 1; counter >= 0; counter--) {
1154                 View v = getView(counter);
1155                 if(pos >= v.getStartOffset() &&
1156                    pos < v.getEndOffset()) {
1157                     return counter;
1158                 }
1159             }
1160             return -1;
1161         }
1162 
1163         /**
1164          * Gets the left inset.
1165          *
1166          * @return the inset
1167          */
1168         protected short getLeftInset() {
1169             View parentView;
1170             int adjustment = 0;
1171             if ((parentView = getParent()) != null) { //use firstLineIdent for the first row
1172                 if (this == parentView.getView(0)) {
1173                     adjustment = firstLineIndent;
1174                 }
1175             }
1176             return (short)(super.getLeftInset() + adjustment);
1177         }
1178 
1179         protected short getBottomInset() {
1180             return (short)(super.getBottomInset() +
1181                            ((minorRequest != null) ? minorRequest.preferred : 0) *
1182                            lineSpacing);
1183         }
1184 
1185         final static int SPACE_ADDON = 0;
1186         final static int SPACE_ADDON_LEFTOVER_END = 1;
1187         final static int START_JUSTIFIABLE = 2;
1188         //this should be the last index in justificationData
1189         final static int END_JUSTIFIABLE = 3;
1190 
1191         int justificationData[] = null;
1192     }
1193 
1194 }