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