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