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