1/*
2 * Copyright (c) 1997, 2017, 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 */
25package javax.swing.text;
26
27import java.util.Arrays;
28import java.awt.*;
29import java.awt.font.TextAttribute;
30import java.awt.geom.Rectangle2D;
31import javax.swing.event.*;
32import 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 */
51public 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}
1186